为什么这种钻石图案存在歧义?

时间:2010-04-17 13:48:44

标签: c++ diamond-problem

#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的定义。因此,它不需要使用Ca->eat()的副本(因此,应该没有问题)。

当我说eat()时(记住eat()不是虚拟的),只有一个A可以调用,A *a = new D();

为什么然后,我是否收到此错误:

  

'A'是'D'

的模糊基础

D *d = new D();究竟对编译器意味着什么?

为什么使用{{1}}?

时不会出现同样的问题

6 个答案:

答案 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();
}

这将分别在eatA的{​​{1}}子对象上调用B

答案 3 :(得分:2)

对于一个真正不寻常的情况,尼尔的答案实际上是错误的(至少部分)。

使用 out 虚拟继承,您将在最终对象中获得两个单独的A副本。

“钻石”在最终对象中生成A的单个副本,并使用虚拟继承由生成

alt text

由于“钻石”表示最终对象中只有一个副本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几乎继承AD的对象大小增加,因为它现在存储2个指针;但是现在只有一个A

所以B::AC::A是相同的,因此D不会发出含糊不清的电话。如果您不使用虚拟继承,则可以使用上面的第二个图表。然后,对A成员的任何调用都会变得模棱两可,您需要指定要采用的路径。

Wikipedia has another good rundown and example here