问题描述
我的理解(例如,读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
我敢肯定有些基本的东西我会被误解或做错了,但我不知道是什么。
解决方法
已构建虚拟库的构造器。它是有条件构造的。即,最派生类的构造函数调用虚拟基数的构造函数。如果-这是条件-具有虚拟基础的派生类不是所构造对象的具体类,则它将不会构造虚拟基础,因为它已由具体类构造。但是否则它将构建虚拟基础。
因此,您必须在所有派生类的构造函数中正确初始化虚拟基类。您只必须知道,如果具体类不是您正在编写的类,则不一定要进行特定的初始化。编译器不会也不知道您是否会创建这些中间类的直接实例,因此它不能简单地忽略它们损坏的构造函数。
如果您将这些中间类抽象化,那么编译器将知道它们永远不是最具体的类型,因此不需要初始化虚拟基的构造函数。
,出于某种原因,构造函数B :: B()和C :: C()试图初始化A(据我所知,这时应该已经由D初始化了):
但是,如果有人单独构建C,编译器应该怎么做?最终对象D
将调用A
的构造函数,但是您将构造函数定义为C
,这意味着可以构造它,但是构造函数出错,因为它不能构造A
。
撇开更复杂的类层次结构,对于任何派生类型,都有一个完全相同的虚拟基础副本。规则是,派生最多的类型的构造函数将以此为基础。编译器必须生成代码来处理该工作:
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
子对象都由不同的构造函数构造。
将责任放在最派生的类型上可以使初始化易于理解和可视化。可能还有其他规则,但这实际上是最简单的。