22

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

基本上,就实际执行的代码而言,virtual operator= 的行为与任何其他虚函数不同。

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])。

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

4

5 回答 5

14

事情是这样的:

如果我将 [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;
}

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

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

于 2009-06-09T10:58:06.323 回答
5

在这种情况下,有三个 operator=:

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&)。

于 2009-06-09T11:18:44.350 回答
4

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

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 &)

==>internally ==> (Object->VTable[Assignement operator]) 获取对象所属类的VTable中赋值运算符的入口,调用方法。

于 2009-06-09T10:58:51.597 回答
3

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

检查此链接以获取有关 operator= 被虚拟化的详细信息。

于 2009-06-09T11:13:06.450 回答
2

原因是编译器提供了默认分配operator=。在场景中调用它,a = b并且我们知道默认内部调用基本赋值运算符。

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

于 2014-11-13T10:38:47.370 回答