views:

210

answers:

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

+6  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:

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";} };
anon
@Neil Butterworth: "copies of `A` in the final object" Why would I copy A in the final object??
Amoeba
@cambr I was using "copy" to mean "instance" - I've changed it.
anon
@Neil Butterworth: Also, I have defined `eat()` explicitly in D. So, it doesn't need to use copies/instance from either `B` or `C`.
Amoeba
@cambr This has nothing to do with the eat() function - try commenting out the call to it.
anon
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.

Mike Dinsdale
@Mike: why do I have `copies of A in your D`. I have defined a seperate `eat()` for D.
Amoeba
It's not to do with `eat()` at all; the call `a->eat()` doesn't itself care what classes are derived from `A` (as you say, `eat` is non-virtual) - it will always call `A::eat`. As @Neil says, remove the call to `eat` and you still have the same problem.
Mike Dinsdale
+2  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.

small_duck
+2  A: 

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.

Johannes Schaub - litb
@Johannes Schaub - litb: okay, I understand what you said. But why does the same problem not occur when I use `D *d = new D();`
Amoeba
@cambr, name lookup stops when you do `d->eat()` in the scope of `D` since it finds `eat` in `D`. It will not touch the `eat` in `A`, `B` or `C` and won't try to call them. So there is no conversion to `A` done in that case, and thus no ambiguity raises up.
Johannes Schaub - litb
If you would try `d->A::eat()` by trying to call `eat` on `A`, you would get the same problem again since it will try to convert `D*` down to an `A*`.
Johannes Schaub - litb
@Johannes Schaub - litb: So, whn I do `A *a = new D();` a new object is created (of type `D`) and then a pointer of type `A` stores the address of this object. Is there a problem yet? Suppose I do not call `eat()`, this is all what happens (creation of an object and assiging of pointer). So, where is the problem in those two steps?
Amoeba
@combr, a pointer of type `A*` does *not* store the address of that object, but the address of one of the `A` objects. The ambigity arises as a choice of the A objects to point to.
Johannes Schaub - litb
+2  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).

Jerry Coffin
You have drawn an object diagram - my "wrong" answer was referring to the inheritance graph, which is a diamond.
anon
@Jerry Coffin: Why does the same problem not occur when I use `D *d = new D();`? In that case also, an object of type `D` is being created and the same problems should occur.
Amoeba
@cambr:because `A *a = new D();` can't decide which `A` sub-object to point at. With `D *d=new D();`, it's going to point at the `D` object not one of the two `A` subjects, so there's no question about where it should point.
Jerry Coffin
+1 for the nice diagram, I think it's the best way to **show** the two `A` in case there is no `virtual` inheritance.
Matthieu M.
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

Brian R. Bondy