为什么派生classes的构造函数要在C++中初始化虚基class?

Why do the constructor of the derived classes want to initialize the virtual base class in C++?

我的理解,例如阅读 this, 是派生 class 的构造函数不调用其虚拟基础 class 的构造函数。

这是我做的一个简单的例子:

class A {
    protected:
        A(int foo) {}
};

class B: public virtual A {
    protected:
        B() {}
};

class C: public virtual A {
    protected:
        C() {}
};

class D: public B, public C {
    public:
        D(int foo, int bar) :A(foo) {}
};


int main()
{
    return 0;
}

出于某种原因,构造函数 B::B()C::C() 正在尝试初始化 A(根据我的理解,D 应该已经初始化了此时):

$ g++ --version
g++ (GCC) 10.2.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ g++ test.cpp
test.cpp: In constructor ‘B::B()’:
test.cpp:8:13: error: no matching function for call to ‘A::A()’
    8 |         B() {}
      |             ^
test.cpp:3:9: note: candidate: ‘A::A(int)’
    3 |         A(int foo) {}
      |         ^
test.cpp:3:9: note:   candidate expects 1 argument, 0 provided
test.cpp:1:7: note: candidate: ‘constexpr A::A(const A&)’
    1 | class A {
      |       ^
test.cpp:1:7: note:   candidate expects 1 argument, 0 provided
test.cpp:1:7: note: candidate: ‘constexpr A::A(A&&)’
test.cpp:1:7: note:   candidate expects 1 argument, 0 provided
test.cpp: In constructor ‘C::C()’:
test.cpp:13:13: error: no matching function for call to ‘A::A()’
   13 |         C() {}
      |             ^
test.cpp:3:9: note: candidate: ‘A::A(int)’
    3 |         A(int foo) {}
      |         ^
test.cpp:3:9: note:   candidate expects 1 argument, 0 provided
test.cpp:1:7: note: candidate: ‘constexpr A::A(const A&)’
    1 | class A {
      |       ^
test.cpp:1:7: note:   candidate expects 1 argument, 0 provided
test.cpp:1:7: note: candidate: ‘constexpr A::A(A&&)’
test.cpp:1:7: note:   candidate expects 1 argument, 0 provided

我确定我误解了或做错了一些非常基本的事情,但我不知道是什么。

虚基的构造器构造完成。它是有条件地构建的。即最派生class的构造函数调用虚基的构造函数。如果-这是条件-具有虚拟基础的派生class不是构造对象的具体class,那么它不会构造虚拟基础,因为它已经被具体[=构造了18=]。但否则它将构建虚拟基地。

因此,您必须在所有派生 class 的构造函数中正确初始化虚拟基 class。您只需要知道特定的初始化不一定会发生,以防具体的 class 不是您正在编写的那个。编译器不会也无法知道您是否会创建这些中间 classes 的直接实例,因此它不能简单地忽略它们损坏的构造函数。

如果你将那些中间 classes 抽象化,那么编译器就会知道它们永远不是最具体的类型,因此它们的构造函数不需要初始化虚拟基。

For some reason, the constructors B::B() and C::C() are trying to initialize A (which, again in my understanding, should have already been initialized by D at this point):

但是如果有人单独构造C,编译器应该怎么办?最终对象D会调用A的构造函数,但你定义构造函数为C,这意味着它可以构造,但构造函数有问题导致它无法构造A

抛开更复杂的 class 层次结构,对于任何派生类型,都有 恰好一个 其虚拟基的副本。规则是 most-derived 类型的构造函数构造该基类。编译器必须生成代码来处理簿记:

struct B { };
struct I1 : virtual B { };
struct I2 : virtual B { };
struct D : I1, I2 { };

B b;   // `B` constructor initializes `B`
I1 i1; // `I1` constructor initializes `B` subobject
I2 i2; // `I2` constructor initializes `B` subobject

到目前为止,很容易想象,因为初始化的方式与 B 不是虚拟基地时的方式相同。

但是你这样做:

D d; // which constructor initializes `B` subobject?

如果基础不是虚拟的,I1 构造函数将初始化它的 B 主题,I2 构造函数将初始化它的 B 子对象。但是因为它是虚拟的,所以只有一个 B 对象。那么哪个构造函数应该初始化它呢?该语言表示 D 构造函数对此负责。

下一个并发症:

struct D1 : D { };
D1 d1; // `D1` constructor initializes `B` subobject

因此,一路上我们创建了五个不同的对象,每个对象都有一个 B 类型的虚拟基类,并且每个对象的 B 子对象都是从不同的构造函数构造的。

将责任放在 most-derived 类型上使初始化易于理解和可视化。可能还有其他规则,但这一条确实是最简单的。