为什么派生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 类型上使初始化易于理解和可视化。可能还有其他规则,但这一条确实是最简单的。
我的理解,例如阅读 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 类型上使初始化易于理解和可视化。可能还有其他规则,但这一条确实是最简单的。