考虑我们有一个父类A,它有受保护的成员x和y以及一个构造函数:
A::A(int xpos, int ypos) : x(xpos), y(ypos) {}
现在考虑我们有两个继承自A的B和C类,并且具有如下定义的构造函数。
class B : public virtual A
B::B(int xpos, int ypos) : A(xpos,ypos) {}
class C : public virtual A
C::C(int xpos, int ypos) : A(xpos,ypos) {}
最后,让我们有一个继承B和C的D类。
class D : public B, public C
如果我按如下方式编写Ds构造函数,我会收到编译错误,说Ds构造函数必须显式调用As构造函数。
D::D(int xpos, int ypos) : B(xpos,ypos), C(xpos,ypos) {}
这是为什么?当我试图继承我对类构造函数一无所知的类时,这肯定会有问题吗?难道B和C都明确地调用As构造函数吗?
答案 0 :(得分:2)
虚拟基础的规则是派生程度最高的类调用其构造函数。如果不是这样的话,哪个中间基类应该构建虚拟基础的一个副本?假装您的类B
的构造函数在构造A
时反转参数的顺序:
B::B(int xpos, int ypos) : A(ypos, xpos) {}
现在,A
子对象的状态应该是什么?第一个构造函数获胜?最后一个获胜?无论哪种方式,它都很脆弱:更改B
类型中C
和D
的顺序会改变A
基类的初始化方式。
答案 1 :(得分:1)
仅B
和C
明确调用A
的构造函数是不够的,因为A
是继承的,而且是虚拟的" 。这意味着A
的单个实例的内容是"共享"在B
和C
之间。
如果C ++允许B
或C
初始化"共享"单方面A
的实例,你可能会得到不一致的行为。考虑如果D
的构造函数在两个初始值设定项中切换xpos
和ypos
的顺序会发生什么:
D::D(int xpos, int ypos) : B(xpos,ypos), C(ypos,xpos) {}
由于B
和C
转发xpos
和ypos
转移到A::A
,A
的实例在B
之间共享并且C
不会以编组方式初始化。这就是为什么D::D
需要明确初始化A
的虚拟实例的原因。
请注意,只有在处理虚拟继承时才需要这样做,因为B
和C
实例的一部分是共享的。
如果我尝试继承我对类构造函数一无所知的类,这肯定会有问题吗?
调用base-of-base构造函数的要求确实打破了封装。但是,当您继承具有虚拟基础的类时,继承者不仅要了解其兄弟基础的结构和行为,还要了解它们之间的相互作用。这就是为什么打破封装可以被认为是合理的,即使它并不理想。