为什么 operator= 和 copy 构造函数在虚拟继承中被区别对待?

Why operator= and copy constructor are treated differently in virtual inheritance?

提问人:Hans 提问时间:11/1/2022 更新时间:11/1/2022 访问量:97

问:

似乎在虚拟继承中,operator= 和 copy 构造函数的处理方式不同。请考虑以下代码:

#include <iostream>
#include <ostream>

class A {
public:
    A(int x) : x(x) {
        std::cout << "A is initialized" << std::endl;
    }

    A(const A& rhs) {
        std::cout << "Copy constructor for A" << std::endl;
    }

    A& operator=(const A& rhs) {
        std::cout << "A::operator=()" << std::endl;
        return *this;
    }

    virtual void funcB() = 0;
    virtual void funcC() = 0;
    int x;
};

class B : virtual public A {
public:
    B(int x) {
        std::cout << "B is initialized" << std::endl;
    }
    B(const B& rhs) {
        std::cout << "Copy constructor for B" << std::endl;
    }
    B& operator=(const B& rhs) {
        std::cout << "B::operator=()" << std::endl;
        return *this;
    }

    void funcB() override {
        std::cout << "B" << std::endl;
    }
    void funcC() override = 0;
};

class C : public B {
public:
    C(int x) : A(x + 1), B(x) {
        std::cout << "C is initialized" << std::endl;
    }
    void funcC() override {
        std::cout << "C" << std::endl;
    }
};

int main() {
    C c(1);
    C c2(c);
    c2 = c;
    std::cout << c.x;
}

这里 B 实际上继承自 A,C 继承自 B。输出为:

A is initialized
B is initialized
C is initialized
Copy constructor for A
Copy constructor for B
B::operator=()
2

我们可以看到 C 的默认复制构造函数已经成功地调用了 B 和 A 的复制构造函数,这就是我想要的。但是默认的 operator= 没有调用 A 的 operator=,这很奇怪。

对此的一个可能的解释是,A 的复制构造函数是由 B 调用的,而不是由 C 调用的。但是,由于我故意将 B 设置为纯虚拟,因此我不必在 B 的复制构造函数中初始化 A,事实上我没有。所以 A 的复制构造函数很可能是从 C 调用的,但我没有证据,因为无论如何 A 都会在 B 之前初始化,无论谁调用它的构造函数。

C++ 复制构造函数 default-constructor

评论

1赞 user253751 11/1/2022
这就是构造函数的工作方式。
0赞 OrenIshShalom 11/1/2022
你能用 A 和 B 来举例吗?真的需要C级吗?
1赞 Hans 11/1/2022
@OrenIshShalom 这是需要的。我需要 B 是抽象的,这样我就不需要从 B 初始化 A(这是合法的,正是我想要的,因为我的最终目标是解决钻石继承问题,我不想在这里详细说明,因为它可能很长)。然后需要 C,因为必须至少有一个具体的类来展示我的问题。
1赞 Hans 11/1/2022
@user253751对不起,我以前误会你了。但我的问题仍然没有解决。我想知道,既然 A 是 C 的一部分,为什么 C 的默认运算符 = 不调用 A 的运算符 =?通常,如果 C 直接继承自 A,它的默认运算符 = 会调用 A 的运算符 =,对吧?
1赞 n. m. could be an AI 11/1/2022
“为什么 C++ 没有功能 X”?---因为没有人非常需要它,所以没有编写添加它的提案。“但我需要它”---很有可能,你认为你需要它,因为你很早就犯了一个设计错误。赋值和多态继承层次结构不能很好地混合。默认情况下,具有虚拟函数的类应禁用复制分配运算符。C++ 没有这样做,这才是真正的问题。

答:

0赞 i chik 11/1/2022 #1

c2 = c;在这里,我们调用 operator=,但类 C 没有它(在您的例子中它不会生成),然后我们从基类 B 调用,它有并且它发生了,因为我们可以将 C 转换为 B。这就是您看到此消息 B::operator=() 的原因。

如果我们调用 operator=,这并不意味着它将从基类调用所有 operator=operator= 是一种与构造函数或析构函数行为不同的方法。如果你想看到 A 类的副本,那么添加 next:

B& operator=(const B& rhs) {
    A::operator=(rhs);
    std::cout << "B::operator=()" << std::endl;
    return *this;
}

因此,如果您没有显式调用 A::operator=,则它不会复制 - 值 x 在初始化后始终相同。

评论

1赞 apple apple 11/1/2022
你还在调用,它是编译器生成的。C::operator=(const C&)
0赞 Hans 11/1/2022
谢谢你的回答。所以基本上,C 的 operator= 被称为继承自 B 的函数?
0赞 apple apple 11/1/2022
@Hans不是真的。
0赞 Hans 11/1/2022
@appleapple 我又感到困惑了。从C++设计者的角度来看,operator=的默认行为应该是“复制这个对象中的所有内容”,如果A是C直接的一部分,我想它的operator=应该被调用吗?为什么没有发生这种情况?
0赞 i chik 11/1/2022
@appleapple,如果它是生成的,则不会看到消息“B::operator=()”,因为方法“C::operator=()”不会显式调用基类的“operator=()”。如果您在 C 类中添加了新的整数字段并且该字段没有复制,则很容易检查。 请看 godbolt.org/z/j1s15e7Mh
3赞 apple apple 11/1/2022 #2

您正在使用编译器生成(即 ),它调用它的所有直接基类(和成员)operator=C::operator=(const C&)operator=

由于不是 的直接基类,因此不调用。ACA::operator=(const A&)

B如果需要,则应复制,与构造函数不同,您可以实现不更改它(或您想要的任何内容)的赋值ABA


另一方面,if 是直接基类A

class C : public B , virtual public A { ... }

然后生成的编译器调用 的赋值运算符(并且 A 是否被多次赋值并不具体operator=A)

评论

0赞 Hans 11/1/2022
明白了。但我仍然对此感到有些不舒服。如果有一个菱形继承 A -> B、C -> D,并且 B 和 C 的 operator= 都调用 A 的 operator=,这是否意味着当我调用 D 的 operator= 时,A 的 operator= 将被调用两次?我认为这种行为是不能容忍的。
1赞 apple apple 11/1/2022
@Hans是的,这是不具体的,而且可能是这种情况。(只是发现我没有在回答:P中完成那句话)
0赞 apple apple 11/1/2022
@Hans相关链接是 timsong-cpp.github.io/cppwp/class#copy.assign-example-3