为什么还要另一个答案?
好吧,关于SO和其他文章的许多帖子都说,钻石问题是通过创建单个实例A
而不是两个实例(每个的父对象一个D
)解决的,从而解决了歧义。但是,这并没有使我对过程有全面的了解,最终我遇到了更多的问题,例如
- 如果
B
并C
尝试创建不同的实例,A
例如使用不同的参数(D::D(int x, int y): C(x), B(y) {}
)调用参数化构造函数,该怎么办?A
将选择的哪个实例成为其一部分D
?
- 如果我将非虚拟继承用于
B
,而将虚拟继承用于C
呢?创建A
in的单个实例是否足够D
?
- 从现在起,我是否应该始终默认使用虚拟继承作为预防措施,因为它以较小的性能成本解决了钻石问题,并且没有其他缺点?
如果不尝试代码示例就无法预测行为,这意味着无法理解该概念。以下是帮助我解决虚拟继承问题的原因。
双A
首先,让我们从没有虚拟继承的代码开始:
#include<iostream>
using namespace std;
class A {
public:
A() { cout << "A::A() "; }
A(int x) : m_x(x) { cout << "A::A(" << x << ") "; }
int getX() const { return m_x; }
private:
int m_x = 42;
};
class B : public A {
public:
B(int x):A(x) { cout << "B::B(" << x << ") "; }
};
class C : public A {
public:
C(int x):A(x) { cout << "C::C(" << x << ") "; }
};
class D : public C, public B {
public:
D(int x, int y): C(x), B(y) {
cout << "D::D(" << x << ", " << y << ") "; }
};
int main() {
cout << "Create b(2): " << endl;
B b(2); cout << endl << endl;
cout << "Create c(3): " << endl;
C c(3); cout << endl << endl;
cout << "Create d(2,3): " << endl;
D d(2, 3); cout << endl << endl;
// error: request for member 'getX' is ambiguous
//cout << "d.getX() = " << d.getX() << endl;
// error: 'A' is an ambiguous base of 'D'
//cout << "d.A::getX() = " << d.A::getX() << endl;
cout << "d.B::getX() = " << d.B::getX() << endl;
cout << "d.C::getX() = " << d.C::getX() << endl;
}
让我们通过输出。执行预期的B b(2);
创建A(2)
,与相同C c(3);
:
Create b(2):
A::A(2) B::B(2)
Create c(3):
A::A(3) C::C(3)
D d(2, 3);
同时需要B
和C
,他们每个人的创造自己的A
,所以我们有双重A
的d
:
Create d(2,3):
A::A(2) C::C(2) A::A(3) B::B(3) D::D(2, 3)
这就是d.getX()
导致编译错误的原因,因为编译器无法选择A
应为其调用方法的实例。仍然可以直接为选定的父类调用方法:
d.B::getX() = 3
d.C::getX() = 2
虚拟性
现在让我们添加虚拟继承。使用相同的代码示例进行以下更改:
class B : virtual public A
...
class C : virtual public A
...
cout << "d.getX() = " << d.getX() << endl; //uncommented
cout << "d.A::getX() = " << d.A::getX() << endl; //uncommented
...
让我们跳到创建d
:
Create d(2,3):
A::A() C::C(2) B::B(3) D::D(2, 3)
可以看到,A
与默认构造函数忽略从构造函数传递参数创建B
和C
。随着歧义消失,所有调用getX()
返回相同的值:
d.getX() = 42
d.A::getX() = 42
d.B::getX() = 42
d.C::getX() = 42
但是,如果我们要为参数调用构造函数该A
怎么办?可以通过从的构造函数中显式调用它来完成D
:
D(int x, int y, int z): A(x), C(y), B(z)
通常,类只能显式地使用直接父级的构造函数,但是虚拟继承的情况除外。发现这个规则对我来说是“单击”的,并帮助您大量了解虚拟接口:
代码class B: virtual A
意味着,任何从其继承的类B
现在都必须A
自行创建,因为B
这不会自动进行。
考虑到这一点,很容易回答我遇到的所有问题:
- 在
D
创作既不B
也不C
负责的参数A
,它完全取决于D
只。
C
将委托A
to的创建D
,但B
将创建自己的实例,A
从而将钻石问题带回
- 在孙子类而不是直子中定义基类参数不是一个好习惯,因此,在存在钻石问题且这种措施不可避免时,应该允许这样做。