[英]Why do the constructor of the derived classes want to initialize the virtual base class in C++?
My understanding, for instance reading this, is that the constructor of a derived class does not call its virtual base class' constructor.我的理解,例如阅读 本文,是派生类的构造函数不会调用其虚拟基类的构造函数。
Here is a simple example I made:这是我制作的一个简单示例:
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;
}
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):出于某种原因,构造函数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
I'm certain there is something very basic I misunderstood or am doing wrong, but I can't figure what.我确定我误解或做错了一些非常基本的事情,但我不知道是什么。
The constructor of virtual base is constructed.虚基的构造函数被构造。 It is constructed conditionally.它是有条件地构建的。 That is, the constructor of the most derived class calls the constructor of the virtual base.即最派生类的构造函数调用虚基类的构造函数。 If - this is the condition - the derived class with virtual base is not the concrete class of the constructed object, then it will not construct the virtual base because it has already been constructed by the concrete class.如果 - 这是条件 - 具有虚拟基类的派生类不是构造对象的具体类,那么它将不会构造虚拟基类,因为它已经被具体类构造了。 But otherwise it will construct the virtual base.但否则它将构建虚拟基地。
So, you must correctly initialise the virtual base class in constructors of all derived classes.因此,您必须在所有派生类的构造函数中正确初始化虚拟基类。 You simply must know that specific initialisation doesn't necessarily happen in case the concrete class is not the one which you are writing.您只需要知道,如果具体类不是您正在编写的类,则不一定会发生特定的初始化。 The compiler doesn't and cannot know whether you will ever create direct instances of those intermediate classes, so it cannot simply ignore their broken constructors.编译器不会也无法知道您是否会创建这些中间类的直接实例,因此它不能简单地忽略它们损坏的构造函数。
If you made those intermediate classes abstract, then the compiler would know that they are never the most concrete type and thus their constructor would not be required to initialise the virtual base.如果你使这些中间类抽象,那么编译器就会知道它们永远不是最具体的类型,因此它们的构造函数不需要初始化虚拟基类。
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):出于某种原因,构造函数 B::B() 和 C::C() 正在尝试初始化 A (在我的理解中,此时应该已经由 D 初始化了):
But what should compiler do if somebody constructs C solo?但是如果有人单独构造 C,编译器应该怎么做? The final object D
will call the constructor of A
but you define constructor to C
which implies that it can be constructed but the constructor is faulty cause it cannot construct A
.最终对象D
将调用A
的构造函数,但您将构造函数定义为C
,这意味着它可以构造但构造函数错误,因为它无法构造A
。
Putting aside more complex class hierarchies, for any derived type there is exactly one copy of its virtual base.撇开更复杂的类层次结构不谈,对于任何派生类型,都只有其虚拟基类的一个副本。 The rule is that the constructor for the most-derived type constructs that base.规则是最派生类型的构造函数构造该基类。 The compiler has to generate code to handle the bookkeeping for that:编译器必须生成代码来处理簿记:
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
So far, it's easy enough to picture, since the initialization is done the same way as it would be if B
was not a virtual base.到目前为止,很容易想象,因为初始化的完成方式与B
不是虚拟基的情况相同。
But then you do this:但是你这样做:
D d; // which constructor initializes `B` subobject?
If the base wasn't virtual, the I1
constructor would initialize its B
subject, and the I2
constructor would initialize its B
subobject.如果基不是虚拟的,则I1
构造函数将初始化其B
主体,而I2
构造函数将初始化其B
子对象。 But because it's virtual, there's only one B
object.但是因为它是虚拟的,所以只有一个B
对象。 So which constructor should initialize it?那么哪个构造函数应该初始化它呢? The language says that the D
constructor is responsible for that.该语言说D
构造函数对此负责。
And the next complication:下一个并发症:
struct D1 : D { };
D1 d1; // `D1` constructor initializes `B` subobject
So, along the way we've created five different objects, each with a virtual base of type B
, and each with the B
subobject being constructed from a different constructor.因此,一路上我们创建了五个不同的对象,每个对象都有一个B
类型的虚拟基类,每个对象的B
子对象都是从不同的构造函数构造的。
Putting the responsibility on the most-derived type makes the initialization easy to understand and to visualize.将责任放在派生最多的类型上使初始化易于理解和可视化。 There could have been other rules, but this one really is the simplest.可能还有其他规则,但这一条确实是最简单的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.