题
#include <iostream>
using namespace std;
class A { public: void eat(){ cout<<"A";} };
class B: public A { public: void eat(){ cout<<"B";} };
class C: 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();
}
我不知道这就是所谓的钻石的问题或没有,但为什么不这项工作?
我已经给出了对于eat()
的D
认定中。因此,它不需要为使用B
的或C
的副本(所以,应该没有问题)。的
:当我说,a->eat()
(记住eat()
不是虚拟的),只有一个可能的eat()
到呼叫,即A
的。强>
那么,为什么我得到这个错误:
'A' 是 'd'
的暧昧基
什么A *a = new D();
平均究竟给编译器??
和
为什么不当我使用D *d = new D();
发生同样的问题?
解决方案
设想一个稍微不同的情形
class A { protected: int a; public: void eat(){ a++; cout<<a;} };
class B: public A { public: void eat(){ cout<<a;} };
class C: public A { public: void eat(){ cout<<a;} };
class D: public B,C { public: void eat(){ cout<<"D";} };
int main(){
A *a = new D();
a->eat();
}
如果这会工作,将它递增a
的B
或a
的C
?这就是为什么它是不明确的。的this
指针和任何非静态数据成员是不同的两个A
子对象(其中之一是由B
子对象包含的,而另一个由C
子对象)。试着改变你这样的代码,它会工作(因为它编译并打印“A”)
class A { public: void eat(){ cout<<"A";} };
class B: public A { public: void eat(){ cout<<"B";} };
class C: public A { public: void eat(){ cout<<"C";} };
class D: public B, public C { public: void eat(){ cout<<"D";} };
int main(){
A *a = static_cast<B*>(new D());
// A *a = static_cast<C*>(new D());
a->eat();
}
这将分别呼吁eat
和A
的B
子对象C
。
其他提示
在金刚石导致在d对象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, public C { public: void eat(){ cout<<"D";} };
请注意编译错误是在 “A * A =新d();”线,而不是在呼叫“吃”。
的问题是,因为您使用的非虚拟继承,则与A类最终两次:一次通过B,并且一旦通过C.例如,如果你添加成员m至A,然后d具有其中的两个: B :: M和C ::米。
有时候,你真的想有一个两倍的推导图,在这种情况下,你总是需要指出其中A你正在谈论。在d,你将能够参考B ::分别M和C ::米。
不过,有时候,你真的想只有一个,在这种情况下,你需要使用的虚拟继承。
对于一个真正不寻常的情况,尼尔的答案其实是错误的(至少部分)。
是退出虚拟继承,你A
的两个单独的副本中的最终目标。
“钻石”导致A
在最终对象的单一拷贝,并产生通过使用虚拟继承:
由于“金刚石”是指只有一个在最终对象A
的复制,以A
参考不产生歧义。如果没有虚拟继承,向A
参考可以指代两个不同的对象(在一个在左边或在图中右边的一个)的
你得到的错误不是从调用eat()
未来 - 这是来自前一行的到来。这是上溯造型本身造成模糊不清。正如尼尔·巴特沃思指出的,在你的A
D
的两个副本,以及编译器不知道你想要哪一个在a
来点。
您想:强>(可实现与虚拟继承)
d,点击 / \结果 B C点击 \ /点击 甲
而非强>(没有虚拟继承会发生什么)
d,点击 / \结果 B C点击 | |点击 A A
虚拟继承意味着将只有1基A
类不2.
您类型D
将有2个虚表指针(你可以看到他们的第一个图表),一个用于B
,一个用于C
谁几乎继承A
。 D
的对象大小增加,因为现在存储2个球;然而,只有一个A
现在。
因此B::A
和C::A
是相同的,所以不可能有从D
无歧义的呼叫。如果你不使用虚拟继承你有上面的第二个图。和A的成员的任何呼叫,则变得模糊,你需要指定要该走的路。