怎么虚拟继承解决"钻石"(多个继承)的模糊性?
-
27-09-2019 - |
题
class A { public: void eat(){ cout<<"A";} };
class B: virtual public A { public: void eat(){ cout<<"B";} };
class C: virtual public A { public: void eat(){ cout<<"C";} };
class D: public B,C { public: void eat(){ cout<<"D";} };
int main(){
A *a = new D();
a->eat();
}
我了解钻石的问题,以及上述段代码没有这样的问题。
究竟如何虚拟继承解决问题吗?
什么我的理解:
当我说 A *a = new D();
, 编译器想知道,如果一个目的类型 D
可以分配给一个指的类型 A
, 但它有两个途径,它可以按照,但不能决定本身。
因此,如何虚拟解决继承问题(协助编译器采取的决定)?
解决方案
您想:强>(可实现与虚拟继承)
A
/ \
B C
\ /
D
而非强>(没有虚拟继承会发生什么)
A A
| |
B C
\ /
D
虚拟继承意味着将只有1基A
类不2.
您类型D
将有2个虚表指针(你可以看到他们的第一个图表),一个用于B
,一个用于C
谁几乎继承A
。 D
的对象大小增加,因为现在存储2个球;然而,只有一个A
现在。
因此B::A
和C::A
是相同的,所以不可能有从D
无歧义的呼叫。如果你不使用虚拟继承你有上面的第二个图。和A的成员的任何呼叫,则变得模糊,你需要指定要该走的路。
其他提示
派生类的实例“包含”基类的实例,因此它们在存储器看起来像:
class A: [A fields]
class B: [A fields | B fields]
class C: [A fields | C fields]
因此,在没有虚拟继承,d类的实例如下所示:
class D: [A fields | B fields | A fields | C fields | D fields]
'- derived from B -' '- derived from C -'
所以,音符两个A数据的“副本”。虚拟继承意味着内部派生类有一个虚函数表指针组在运行时,它指向基类的数据,以使得B,C和d类的实例如下所示:
class B: [A fields | B fields]
^---------- pointer to A
class C: [A fields | C fields]
^---------- pointer to A
class D: [A fields | B fields | C fields | D fields]
^---------- pointer to B::A
^--------------------- pointer to C::A
为什么另一个答案吗?
嗯,很多职位上,使和文章外说,钻石问题的解决是通过创建单一实例 A
而不是两个(一个用于每个父母的 D
),从而解决的模糊性。然而,这并没有给我全面的了解的过程中,我结束了甚至更多这样的问题
- 如果有什么
B
和C
试图建立不同的实例A
例如调用的参数化构造使用不同的参数(D::D(int x, int y): C(x), B(y) {}
)?其实例A
将会选择成为的一部分D
? - 如果我使用非虚拟的继承
B
, 但虚拟一个C
?它是足够的,用于创建单一实例A
在D
? - 我应该总是使用虚拟继承通过默认从现在作为预防性措施,因为它解决了可能的钻石问题与较小的性能的成本和没有其它的缺点?
不能够预测的行为,而不试图代码样本的装置不理解的概念。下面是什么帮我包裹头周围的虚拟继承。
双一个
首先,让我们开始用这个代码没有虚拟的继承:
#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
要D
, 但B
将创建自己的实例A
由此带来的钻石问题的回来- 确定基类参数的孙子类,而不是直接的孩子不是一个好的做法,所以它应该能容忍的时钻石问题的存在和这种措施是不可避免的。
问题是不是路径编译器必须遵循。问题是端点该路径的:铸造的结果。当涉及到类型转换,该路径并不重要,只有最后的结果一样。
如果你使用普通的继承,每条路径都有其自己独特的端点,这意味着,中投的结果是不明确的,这就是问题所在。
如果你使用虚拟继承,你会得到一个菱形的层次结构:两个路径导致同样的端点。在这种情况下,不再选择的路径存在问题(或者,更准确地说,不再是问题),因为这两个路径导致同样的结果。其结果是不再暧昧 - 这是最重要的。的确切路径没有。
实际上,例如应该是如下:
#include <iostream>
//THE DIAMOND PROBLEM SOLVED!!!
class A { public: virtual ~A(){ } virtual void eat(){ std::cout<<"EAT=>A";} };
class B: virtual public A { public: virtual ~B(){ } virtual void eat(){ std::cout<<"EAT=>B";} };
class C: virtual public A { public: virtual ~C(){ } virtual void eat(){ std::cout<<"EAT=>C";} };
class D: public B,C { public: virtual ~D(){ } virtual void eat(){ std::cout<<"EAT=>D";} };
int main(int argc, char ** argv){
A *a = new D();
a->eat();
delete a;
}
...这样的输出是会是正确的: “EAT => d”
虚拟继承只解决了爷爷的重复! 但你仍然需要指定方法是虚拟的,以获得正确的方法... overrided