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

Dav*_*eas 22 c++ inheritance virtual-functions assignment-operator

在玩实现虚拟赋值运算符的过程中,我以一种有趣的行为结束了.它不是编译器故障,因为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 &)
}
Run Code Online (Sandbox Code Playgroud)

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

对这种奇怪的行为有什么明智的解释吗?

Jas*_*siu 14

这是怎么回事:

如果我改变[1]

a = *((Base*)&b);
Run Code Online (Sandbox Code Playgroud)

然后事情就像你期望的那样.有一个自动生成的赋值运算符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;
}
Run Code Online (Sandbox Code Playgroud)

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

其他结果可以用同样的方法解释.

  • 这里没有猜测.规则非常严格. (3认同)

MSa*_*ers 5

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

Base::operator=(Base const&) // virtual
Derived::operator=(Base const&) // virtual
Derived::operator=(Derived const&) // Compiler generated, calls Base::operator=(Base const&) directly
Run Code Online (Sandbox Code Playgroud)

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