#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 *a = new D();
。
为什么然后,我是否收到此错误:
'A'是'D'
的模糊基础
D *d = new D();
究竟对编译器意味着什么?
和
为什么使用{{1}}?
时不会出现同样的问题答案 0 :(得分:6)
钻石在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";} };
答案 1 :(得分:2)
请注意,编译错误在“A * a = new D();”上这一行,而不是“吃”的号召。
问题是因为你使用了非虚拟继承,你最终得到了A类两次:一次通过B,一次通过C.例如,你将一个成员m添加到A,然后D有两个: B :: m,和C :: m。
有时,你真的希望在推导图中有两次A,在这种情况下,你总是需要指出你在说的是哪一个A.在D中,您可以分别引用B :: m和C :: m。
但有时候,你真的只想要一个A,在这种情况下你需要使用virtual inheritance。
答案 2 :(得分:2)
想象一下略有不同的情景
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
的{{1}}子对象上调用B
。
答案 3 :(得分:2)
对于一个真正不寻常的情况,尼尔的答案实际上是错误的(至少部分)。
使用 out 虚拟继承,您将在最终对象中获得两个单独的A
副本。
“钻石”在最终对象中生成A
的单个副本,并使用虚拟继承由生成:
由于“钻石”表示最终对象中只有一个副本A
,因此对A
的引用不会产生歧义。如果没有虚拟继承,对A
的引用可以引用两个不同对象中的任何一个(左侧的对象或图中右侧的对象)。
答案 4 :(得分:0)
您收到的错误并非来自致电eat()
- 它来自之前的线路。它是向上的本身,造成了歧义。正如Neil Butterworth所指出的那样,A
中有两个D
副本,编译器不知道您希望a
指向哪一个。
答案 5 :(得分:0)
您想要:(可通过虚拟继承获得)
D
/ \
B C
\ /
一个
而不是:(没有虚拟继承会发生什么)
D
/ \
B C
| |
A A
虚拟继承意味着只有1个基本A
类的实例不是2.
您的类型D
将有2个vtable指针(您可以在第一个图表中看到它们),一个用于B
,另一个用于C
几乎继承A
。 D
的对象大小增加,因为它现在存储2个指针;但是现在只有一个A
。
所以B::A
和C::A
是相同的,因此D
不会发出含糊不清的电话。如果您不使用虚拟继承,则可以使用上面的第二个图表。然后,对A成员的任何调用都会变得模棱两可,您需要指定要采用的路径。