【问题标题】:Do derived classes indirectly inherit base's assignment operator?派生类是否间接继承基的赋值运算符?
【发布时间】:2012-02-10 15:41:40
【问题描述】:

我试图理解这种行为,但似乎没有。请看这段代码:

#include <iostream>
using namespace std;

class Base
{
public:
    void operator=(const Base& rf)
    {
        cout << "base operator=" << endl;
        this->y = rf.y;
    }
    int y;
    Base() : y(100) { }
};

class Derived : public Base
{
public:
    int x;
    Derived() : x(100) { }
};

int main()
{
    Derived test;
    Derived test2;
    test2.x = 0;
    test2.y = 0;
    test.operator=(test2); // operator auto-generated for derived class but...
    cout << test.x << endl << test.y << endl;
    cin.ignore();
    return 0;
}

程序输出:

> base operator=
>  0
>  0

现在我感到困惑的是: 该规则规定派生类从不继承赋值运算符,而是创建自己的operator=,但在此示例中,基类的operator= 会在派生类上调用。

其次,我能够在派生类上显式调用赋值运算符,而派生类中并未显式定义该赋值运算符。

现在,如果我理解正确,这意味着任何用户定义的基运算符总是在派生类上被调用?

【问题讨论】:

    标签: c++ inheritance assignment-operator


    【解决方案1】:

    编译器生成的赋值运算符调用每个子对象的赋值运算符。这包括基类和非静态成员变量。

    标准说(第 12.8 节 [class.copy]):

    如果类定义没有显式声明复制赋值运算符,则隐式声明。如果类定义声明了移动构造函数或移动赋值运算符,则隐式声明的复制赋值运算符被定义为删除;否则,它被定义为默认值(8.4)。如果类具有用户声明的复制构造函数或用户声明的析构函数,则不推荐使用后一种情况。类X 的隐式声明的复制赋值运算符将具有以下形式

    X&  X::operator=(const  X&)
    

    如果

    • X 的每个直接基类B 都有一个复制赋值运算符,其参数类型为const B&amp;const volatile B&amp;B,以及
    • 对于X 中所有属于M(或其数组)类类型的非静态数据成员,每个此类类型都有一个复制赋值运算符,其参数类型为const M&amp;、@987654332 @ 或 M

    否则,隐式声明的复制赋值运算符将具有以下形式

    X&  X::operator=(X&)
    

    非联合类X 的隐式定义的复制/移动赋值运算符执行其子对象的成员复制/移动赋值。首先分配X 的直接基类,按照它们在base-specifier-list 中的声明顺序,然后分配X 的直接非静态数据成员,在它们在类定义中的声明顺序。让x 成为函数的参数 或者,对于移动运算符,引用参数的 xvalue。每个子对象都以适合其类型的方式分配:

    • 如果子对象是类类型,就好像通过调用operator=,将子对象作为对象表达式,x 的相应子对象作为单个函数参数(好像通过显式限定;也就是说,忽略更多派生类中任何可能的虚拟覆盖函数);
    • 如果子对象是一个数组,则以适合元素类型的方式分配每个元素;
    • 如果子对象是标量类型,则使用内置赋值运算符。

    未指定表示虚拟基类的子对象是否由隐式定义的复制赋值运算符多次赋值。

    【讨论】:

    • 这是否意味着用户定义的运算符不会调用子对象的运算符?你是说只有编译器生成的运算符调用的子对象的运算符?
    • @codekiddy:除非您自己调用基类版本,否则编译器不会为您这样做。
    【解决方案2】:

    生成的会自动调用基类赋值运算符。

    // generated version looks basically like this
    Derived& operator=(Derived const& other){
      Base::operator=(static_cast<Base const&>(other));
      x = other.x;
      return *this;
    }
    

    演员在那里是为了避免意外调用模板化的Base::operator=,就像这样:

    template<class Other>
    Base& operator=(Other const& other); // accepts everything
    

    或者像这样一个奇怪的:

    // forward-declare 'Derived' outside of 'Base'
    Base& operator=(Derived const& other); // accepts derived class (for whatever reason)
    

    其次,我能够在派生类上显式调用赋值运算符,而派生类中并未显式定义该赋值运算符。

    编译器自动声明一个赋值运算符,如果你不这样做并且你的类允许它(即,没有引用成员和一些其他神秘的规则),另外定义它如果你实际上在某个地方使用它。

    【讨论】:

      【解决方案3】:

      这是因为隐式定义的 operator = 调用了基类 operator =。请参阅 FAQ 精简版:

      I'm creating a derived class; should my assignment operator call my base class's assignment operator?

      是的(如果您需要首先定义赋值运算符)。

      如果您定义自己的赋值运算符,编译器不会自动为您调用基类的赋值运算符。除非您的基类的赋值运算符本身被破坏,否则您应该从派生类的赋值运算符中显式调用它(同样,假设您首先创建了一个)。

      但是,如果您不创建自己的赋值运算符,编译器为您创建的赋值运算符将自动调用您的基类的赋值运算符。

      【讨论】:

        【解决方案4】:

        4 东西永远不会被继承 构造函数 复制构造函数 赋值运算符 析构函数

        即使您没有编写赋值运算符,您的代码也可以正常工作。 每当您使用赋值运算符时,编译器都会确保调用每个成员对象和基类的赋值运算符。

        【讨论】:

        • 这不是真的,请参阅我的答案下方的 cmets。
        • @Ben- 可能是我没有解释整个事情;我说的是编译器提供的函数。
        猜你喜欢
        • 2012-02-12
        • 2021-08-28
        • 2014-10-27
        • 2012-02-28
        • 1970-01-01
        • 2016-07-10
        • 1970-01-01
        • 1970-01-01
        • 2013-03-03
        相关资源
        最近更新 更多