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

我不确定这是否称为钻石问题,但为什么这不起作用?

我已经给出了 for 的eat()定义D。所以,它不需要使用B's 或C' 副本(所以,应该没有问题)。

当我说a->eat()(记住eat()不是虚拟的)时,只有一种可以eat()调用,即A.

那么,为什么我会收到此错误:

'A' 是 'D' 的模棱两可的基础


A *a = new D();对编译器到底意味着什么?

为什么我使用时不会出现同样的问题D *d = new D();

4

6 回答 6

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";} };
于 2010-04-17T13:54:02.067 回答
2

请注意,编译错误出现在“A *a = new D();”上 行,不就叫“吃”。

问题在于,因为您使用了非虚拟继承,所以您最终得到了两次类 A:一次通过 B,一次通过 C。例如,如果您将成员 m 添加到 A,则 D 有其中两个:B:: m 和 C::m。

有时,您确实希望在推导图中出现两次 A,在这种情况下,您总是需要指出您正在谈论的是哪个 A。在 D 中,您可以分别引用 B::m 和 C::m。

但是,有时您真的只想要一个 A,在这种情况下您需要使用虚拟继承

于 2010-04-17T13:59:57.973 回答
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();
}

如果这可行,它会增加ainB还是ain C?这就是为什么它是模棱两可的。对于两个子对象(其中一个包含在子对象中,另一个包含在子对象中),this指针和任何非静态数据成员都是不同的。尝试像这样更改您的代码,它将起作用(因为它编译并打印“A”)ABC

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子对象。BC

于 2010-04-17T14:04:20.783 回答
2

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

如果没有虚拟继承,您将在A最终对象中获得两个单独的副本。

A“钻石”在最终对象中产生一个副本,并通过使用虚拟继承产生:

替代文字

由于“钻石”意味着最终对象中只有一个副本,A因此对的引用不会A产生歧义。如果没有虚拟继承,对的引用A可以引用两个不同对象中的任何一个(图中左侧的一个或右侧的一个)。

于 2010-04-17T14:13:03.377 回答
0

你得到的错误不是来自调用eat()- 它来自之前的线路。造成歧义的是向上转型本身。正如 Neil Butterworth 指出的那样,A您的 中有两个副本D,编译器不知道您要a指向哪个副本。

于 2010-04-17T13:57:13.360 回答
0

您想要:(可通过虚拟继承实现)

  D
  / \
B C
  \ /
  A

而不是:(没有虚拟继承会发生什么)

    D
   / \
  B C
  | |
  一个

虚拟继承意味着只有 1 个基A类实例而不是 2 个。

您的类型D将有 2 个 vtable 指针(您可以在第一张图中看到它们),一个用于虚拟继承B,一个用于C虚拟继承AD的对象大小增加了,因为它现在存储了 2 个指针;但是现在只有一个A

所以B::AC::A是相同的,因此不会有来自 的模棱两可的调用D。如果你不使用虚拟继承,你有上面的第二张图。然后对 A 成员的任何调用都会变得模棱两可,您需要指定要采用的路径。

维基百科有另一个很好的纲要和例子在这里

于 2010-04-17T17:12:14.703 回答