代码之家  ›  专栏  ›  技术社区  ›  David Rodríguez - dribeas

为什么虚拟赋值的行为不同于具有相同签名的其他虚拟函数?

  •  21
  • David Rodríguez - dribeas  · 技术社区  · 15 年前

    在玩虚拟赋值操作符时,我以一个有趣的行为结束。这不是编译器的小故障,因为g++4.1、4.3和VS2005具有相同的行为。

    就实际执行的代码而言,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 &)
    }
    

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

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

    5 回复  |  直到 11 年前
        1
  •  14
  •   Jasiu    15 年前

    事情是这样的:

    如果我将[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 衍生 因此,他们选择使用上面自动生成的运算符调用您的运算符。你就是这样得到的[1]。我的指针转换强制编译器按照您的方式执行,因为我告诉编译器“忘记”这一点 B 衍生 所以它使用 Base .

        2
  •  5
  •   David Rodríguez - dribeas    15 年前

    在这种情况下,有三个运算符=:

    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&,因此无法调用派生::运算符=(派生&)。

        3
  •  4
  •   M.M    8 年前

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

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

    ==>内部==>(对象->VTable[赋值运算符])

        4
  •  3
  •   M.M    8 年前

    如果您未能提供适当的 operator= 接线员= 由重载任何用户定义的编译器提供。在你的情况下,它将调用 Base::operator= (Base const& ) 在复制派生成员之前。

    检查这个 link

        5
  •  2
  •   Community Egal    7 年前

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

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