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

时间:2020-11-03 02:06:20

标签: c++ constructor virtual-inheritance ctor-initializer

我的理解(例如,读this,是,派生类的构造函数没有调用其虚拟基类的构造函数。

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

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

我敢肯定有些基本的东西我会被误解或做错了,但我不知道是什么。

3 个答案:

答案 0 :(得分:4)

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

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

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

答案 1 :(得分:3)

出于某种原因,构造函数B :: B()和C :: C()试图初始化A(据我所知,这时应该已经由D初始化了):

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

答案 2 :(得分: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子对象都由不同的构造函数构造。

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

相关问题