8

例如,我的理解是派生类的构造函数不会调用其虚拟基类的构造函数。

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

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

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

4

3 回答 3

4

构造虚基构造函数。它是有条件地构建的。即最派生类的构造函数调用虚基的构造函数。如果 - 这是条件 - 具有虚拟基的派生类不是构造对象的具体类,那么它将不会构造虚拟基,因为它已经由具体类构造。但否则它将构建虚拟基地。

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

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

于 2020-11-03T02:15:09.283 回答
3

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

但是如果有人单独构造 C,编译器应该怎么做?最终对象D将调用的构造函数,A但您定义了构造函数,C这意味着它可以被构造,但构造函数有错误,因为它无法构造A

于 2020-11-03T02:19:32.493 回答
0

撇开更复杂的类层次结构不谈,对于任何派生类型,它的虚拟基类都只有一个副本。规则是最派生类型的构造函数构造该基础。编译器必须生成代码来处理簿记:

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子对象都是由不同的构造函数构造的。

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

于 2020-11-03T15:01:19.810 回答