为什么这个菱形图案有歧义?
#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()
的定义。因此,它不需要使用 B
或 C
的副本(因此,应该没有问题)。
当我说,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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
菱形会在 D 对象中产生 A 的两个实例,并且您指的是哪一个是不明确的 - 您需要使用虚拟继承来解决此问题:
假设您实际上只需要一个实例。我还假设你的真正意思是:
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:
assuming that you actually only wanted one instance. I also assume you really meant:
想象一个稍微不同的场景
如果这可行,它会增加
B
中的a
或C
中的a
?这就是为什么它是暧昧的。对于两个A
子对象(其中一个由B
子对象包含,并且另一个由C
子对象实现)。尝试像这样更改您的代码,它将起作用(因为它编译并打印“A”),这将在
B
A 子对象上调用eat
分别是和
C
。Imagine a slightly different scenario
If this would work, would it increment the
a
inB
or thea
inC
? That's why it's ambiguous. Thethis
pointer and any non-static data member is distinct for the twoA
subobjects (one of which is contained by theB
subobject, and the other by theC
subobject). Try changing your code like this and it will work (in that it compiles and prints "A")That will call
eat
on theA
subobject ofB
andC
respectively.请注意,编译错误位于“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.
对于真正不寻常的情况,尼尔的答案实际上是错误的(至少部分是错误的)。
通过out虚拟继承,您可以在最终对象中获得
A
的两个独立副本。“钻石”在最终对象中生成
A
的单个副本,并使用虚拟继承生成:由于“钻石”意味着最终对象中只有 一个
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:Since "the diamond" means there's only one copy of
A
in the final object, a reference toA
produces no ambiguity. Without virtual inheritance, a reference toA
could refer to either of two different objects (the one on the left or the one on the right in the diagram).您收到的错误不是来自调用
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 ofA
in yourD
, and the compiler doesn't know which one you wanta
to point at.您想要:(可通过虚拟继承实现)
而不是:(没有虚拟继承会发生什么)
继承意味着只有 1 个基
A
类的实例,而不是 2 个。您的类型
D
将有 2 个 vtable 指针(您可以在第一个图中看到它们) ),一个用于B
,一个用于虚拟继承A
的C
。D
的对象大小增加了,因为它现在存储了 2 个指针;然而现在只有一个A
。因此,
B::A
和C::A
是相同的,因此来自D
的调用不会有歧义。如果您不使用虚拟继承,您将看到上面的第二张图。然后,对 A 成员的任何调用都会变得不明确,您需要指定要采用的路径。维基百科在这里还有另一个很好的概述和示例
You want: (Achievable with virtual inheritance)
And not: (What happens without virtual inheritance)
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 forB
and one forC
who virtually inheritA
.D
's object size is increased because it stores 2 pointers now; however there is only oneA
now.So
B::A
andC::A
are the same and so there can be no ambiguous calls fromD
. 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