为什么这个菱形图案有歧义?

发布于 2024-08-29 13:53:35 字数 855 浏览 5 评论 0原文

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

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

我已经为 D 给出了 eat() 的定义。因此,它不需要使用 BC 的副本(因此,应该没有问题)。

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

为什么我会收到此错误:

“A”是“D”的不明确基数


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

为什么

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

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

I am not sure this is called diamond problem or not, but why doesn't this work?

I have given the defination for eat() for D. So, it doesn't need to use either B's or C's copy (so, there should be no problem).

When I said, a->eat() (remember eat() is not virtual), there is only one possible eat() to call, that of A.

Why then, do I get this error:

'A' is an ambiguous base of 'D'


What exactly does A *a = new D(); mean to the compiler??

and

Why does the same problem not occur when I use D *d = new D();?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

挖鼻大婶 2024-09-05 13:53:35

菱形会在 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";} };

The diamond results in TWO instances of A in the D object, and it is ambiguous which one you are referring to - you need to use virtual inheritance to solve this:

class B: virtual public A   { public: void eat(){ cout<<"B";} };
class C: virtual public A   { public: void eat(){ cout<<"C";} };

assuming that you actually only wanted one instance. I also assume you really meant:

class D: public B, public C { public: void eat(){ cout<<"D";} };
孤蝉 2024-09-05 13:53:35

想象一个稍微不同的场景

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

如果这可行,它会增加 B 中的 aC 中的 a ?这就是为什么它是暧昧的。对于两个 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();
}

这将在 BA 子对象上调用 eat分别是 C

Imagine a slightly different scenario

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

If this would work, would it increment the a in B or the a in C? That's why it's ambiguous. The this pointer and any non-static data member is distinct for the two A subobjects (one of which is contained by the B subobject, and the other by the C subobject). Try changing your code like this and it will work (in that it compiles and prints "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();
}

That will call eat on the A subobject of B and C respectively.

那一片橙海, 2024-09-05 13:53:35

请注意,编译错误位于“A *a = new D();”行,而不是叫“吃饭”。

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

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

但有时,您确实只需要一个 A,在这种情况下,您需要使用 虚拟继承

Note that the compile error is on the "A *a = new D();" line, not on the call to "eat".

The problem is that because you used non-virtual inheritance, you end up with class A twice: once through B, and once through C. If for example you add a member m to A, then D has two of them: B::m, and C::m.

Sometimes, you really want to have A twice in the derivation graph, in which case you always need to indicate which A you are talking about. In D, you would be able to reference B::m and C::m separately.

Sometimes, though, you really want only one A, in which case you need to use virtual inheritance.

不顾 2024-09-05 13:53:35

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

通过out虚拟继承,您可以在最终对象中获得A的两个独立副本。

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

alt text

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

For a truly unusual situation, Neil's answer is actually wrong (at least partly).

With out virtual inheritance, you get two separate copies of A in the final object.

"The diamond" results in a single copy of A in the final object, and is produced by using virtual inheritance:

alt text

Since "the diamond" means there's only one copy of A in the final object, a reference to A produces no ambiguity. Without virtual inheritance, a reference to A could refer to either of two different objects (the one on the left or the one on the right in the diagram).

梦途 2024-09-05 13:53:35

您收到的错误不是来自调用 eat() - 它来自之前的行。正是这种向上转型本身造成了歧义。正如 Neil Butterworth 指出的,D 中有两个 A 副本,而编译器不知道您想要 a 到哪一个指向.

The error you're getting isn't coming from calling eat() - it's coming from the line before. It's the upcast itself that creates the ambiguity. As Neil Butterworth points out, there are two copies of A in your D, and the compiler doesn't know which one you want a to point at.

七婞 2024-09-05 13:53:35

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

  D
  / \
乙C
  \ /
 一个

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

    D
    /   \
 乙C
  |     |
  A  一个

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

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

因此,B::AC::A 是相同的,因此来自 D 的调用不会有歧义。如果您不使用虚拟继承,您将看到上面的第二张图。然后,对 A 成员的任何调用都会变得不明确,您需要指定要采用的路径。

维基百科在这里还有另一个很好的概述和示例

You want: (Achievable with virtual inheritance)

  D
  / \
B   C
  \ /
  A

And not: (What happens without virtual inheritance)

    D
   /   \
  B   C
  |     |
  A   A

Virtual inheritance means that there will be only 1 instance of the base A class not 2.

Your type D would have 2 vtable pointers (you can see them in the first diagram), one for B and one for C who virtually inherit A. D's object size is increased because it stores 2 pointers now; however there is only one A now.

So B::A and C::A are the same and so there can be no ambiguous calls from D. If you don't use virtual inheritance you have the second diagram above. And any call to a member of A then becomes ambiguous and you need to specify which path you want to take.

Wikipedia has another good rundown and example here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文