开发者

Why is there ambiguity in this diamond pattern?

#include <iostream>
using namespace std;

class A             { public: void eat(){ cout<&开发者_高级运维lt;"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();?


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";} };


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.


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.


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:

Why is there ambiguity in this diamond pattern?

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).


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.


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜