为什么虚拟分配的行为与同一签名的其他虚拟功能不同?

Why does virtual assignment behave differently than other virtual functions of the same signature?

提问人:David Rodríguez - dribeas 提问时间:6/9/2009 最后编辑:curiousguyDavid Rodríguez - dribeas 更新时间:9/7/2016 访问量:3758

问:

在玩实现虚拟赋值运算符时,我以一个有趣的行为结束了。这不是编译器故障,因为 g++ 4.1、4.3 和 VS 2005 具有相同的行为。

基本上,虚拟运算符 = 的行为与任何其他虚函数在实际执行的代码方面不同。

struct Base {
   virtual Base& f( Base const & ) {
      std::cout << "Base::f(Base const &)" << std::endl;
      return *this;
   }
   virtual Base& operator=( Base const & ) {
      std::cout << "Base::operator=(Base const &)" << std::endl;
      return *this;
   }
};
struct Derived : public Base {
   virtual Base& f( Base const & ) {
      std::cout << "Derived::f(Base const &)" << std::endl;
      return *this;
   }
   virtual Base& operator=( Base const & ) {
      std::cout << "Derived::operator=( Base const & )" << std::endl;
      return *this;
   }
};
int main() {
   Derived a, b;

   a.f( b ); // [0] outputs: Derived::f(Base const &) (expected result)
   a = b;    // [1] outputs: Base::operator=(Base const &)

   Base & ba = a;
   Base & bb = b;
   ba = bb;  // [2] outputs: Derived::operator=(Base const &)

   Derived & da = a;
   Derived & db = b;
   da = db;  // [3] outputs: Base::operator=(Base const &)

   ba = da;  // [4] outputs: Derived::operator=(Base const &)
   da = ba;  // [5] outputs: Derived::operator=(Base const &)
}

其效果是,虚拟运算符=的行为与具有相同签名的任何其他虚函数([0]与[1]相比)不同,当通过实际派生对象([1])或派生引用([3])调用时,它调用运算符的基本版本,而当通过基本引用([2])调用时,它确实作为常规虚函数执行, 或者当左值或右值是基引用,另一个是派生引用 ([4],[5]) 时。

对于这种奇怪的行为有什么合理的解释吗?

C++ 继承 virtual-functions 赋值运算符

评论


答:

14赞 Jasiu 6/9/2009 #1

事情是这样的:

如果我将 [1] 更改为

a = *((Base*)&b);

然后事情就会按照你期望的方式进行。有一个自动生成的赋值运算符,如下所示:Derived

Derived& operator=(Derived const & that) {
    Base::operator=(that);
    // rewrite all Derived members by using their assignment operator, for example
    foo = that.foo;
    bar = that.bar;
    return *this;
}

在您的示例中,编译器有足够的信息来猜测该信息,并且属于类型,因此他们选择使用上面自动生成的运算符来调用您的运算符。这就是你得到 [1] 的方式。我的指针强制编译器按照你的方式去做,因为我告诉编译器“忘记”它是类型的,所以它使用 .abDerivedbDerivedBase

其他结果可以用同样的方式解释。

评论

3赞 MSalters 6/9/2009
这里不涉及猜测。规则非常严格。
0赞 David Rodríguez - dribeas 6/9/2009
谢谢,真正的答案(已经有三个人发布)是编译器为 Derived 类生成的 operator= 隐式调用 Base::operator=。我将此标记为“接受的答案”,因为它是第一个。
0赞 M.M 9/7/2016
a = static_cast<Base &>(b);将是避免 C 型演员表的一种方式(这有意外重新解释演员表的风险)
4赞 aJ. 6/9/2009 #2

没有为派生类定义用户提供的赋值运算符。因此,编译器合成一个,并且从派生类的该合成赋值运算符中调用内部基类赋值运算符。

virtual Base& operator=( Base const & ) //is not assignment operator for Derived

因此a = b; // [1] outputs: Base::operator=(Base const &)

在派生类中,基类赋值运算符已被重写,因此,重写的方法在派生类的虚拟表中获取一个条目。当通过引用或指针调用该方法时,由于运行时的 VTable 条目解析,将调用派生类重写的方法。

ba = bb;  // [2] outputs: Derived::operator=(Base const &)

==>内部 ==> (Object->VTable[赋值运算符]) 在 VTable 中获取对象所属类的赋值运算符条目并调用该方法。

3赞 Abhay 6/9/2009 #3

如果未能提供适当的(即正确的返回和参数类型),则默认值由编译器提供,该编译器会重载任何用户定义的类型。在你的例子中,它将在复制派生成员之前调用。operator=operator=Base::operator= (Base const& )

查看此链接,了解有关 operator= 虚拟化的详细信息。

5赞 MSalters 6/9/2009 #4

在本例中,有三个运算符=:

Base::operator=(Base const&) // virtual
Derived::operator=(Base const&) // virtual
Derived::operator=(Derived const&) // Compiler generated, calls Base::operator=(Base const&) directly

这就解释了为什么在 [1] 中,Base::operator=(Base const&) 被称为“虚拟”。它是从编译器生成的版本调用的。这同样适用于案例[3]。在情况 2 中,右侧参数 'bb' 的类型为 Base&,因此无法调用 Derived::operator=(Derived&)。

2赞 sorv3235055 11/13/2014 #5

原因是编译器提供了默认赋值。 在场景中调用 a = b,正如我们所知,default 在内部调用基本赋值运算符。operator=

有关虚拟分配的更多说明,请访问:https://stackoverflow.com/a/26906275/3235055