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

时间:2009-06-09 10:20:16

标签: 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 &)
}

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

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

5 个答案:

答案 0 :(得分: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;
}

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

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

答案 1 :(得分:5)

在这种情况下有三个operator =

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

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

答案 2 :(得分:4)

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

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

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

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

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

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

答案 3 :(得分:3)

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

检查此link以获取有关operator =正在虚拟的详细信息。

答案 4 :(得分:2)

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

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