Sign in to follow this  
MARS_999

virtual keyword in derived classes and multiple inheritance

Recommended Posts

MARS_999    1627
I am not sure why I need to do this but if I don't it states D

ambiguous conversions from D* to A*

but as I look at the code D is a derived class from A as AA is a derived class from A and D should have all the properties as A correct?

[code]
//this code works....
class A
{
public:
A(){}
virtual ~A()
{
std::cout << "A\n";
}
virtual void Print(void) = 0;
};
class B : virtual public A//need the virtual in derived class to stop duplicate base classes in multiple inheritance
{
public:
B(){}
virtual ~B()
{
std::cout << "B\n";
}
virtual void Print(void)
{
std::cout << "B\n";
}
};
class C : public B
{
public:
C(){}
virtual ~C()
{
std::cout << "C\n";
}
};
class AA : virtual public A
{
public:
AA(){}
virtual ~AA()
{
std::cout << "AA\n";
}
virtual void Print(void)
{
std::cout << "AA\n";
}
};
class D : public AA, public B
{
public:
D(){}
virtual ~D()
{
std::cout << "D\n";
}
virtual void Print(void)
{
std::cout << "D\n";
}
};


int main(int argc, const char* argv[])
{
A* a = nullptr;
B* b = new B();
C* c = new C();//left Print() out to show it will default to B
D* d = new D();

a = b;
a->Print();std::cout << std::endl;
a = c;
a->Print();std::cout << std::endl;
a = d;// if you take out the virtual in the base classes of class D e.g. AA and B you can't do this...
a->Print();std::cout << std::endl;std::cout << std::endl;

delete b;
std::cout << std::endl;
delete c;
std::cout << std::endl;
delete d;

return 0;
}

[/code]

code that spits out the error

[code]
class A
{
public:
A(){}
virtual ~A()
{
std::cout << "A\n";
}
virtual void Print(void) = 0;
};
class B : virtual public A//need the virtual in derived class to stop duplicate base classes in multiple inheritance
{
public:
B(){}
virtual ~B()
{
std::cout << "B\n";
}
virtual void Print(void)
{
std::cout << "B\n";
}
};
class C : public B
{
public:
C(){}
virtual ~C()
{
std::cout << "C\n";
}
};
class AA : public A
{
public:
AA(){}
virtual ~AA()
{
std::cout << "AA\n";
}
virtual void Print(void)
{
std::cout << "AA\n";
}
};
class D : public AA, public B
{
public:
D(){}
virtual ~D()
{
std::cout << "D\n";
}
virtual void Print(void)
{
std::cout << "D\n";
}
};
[/code] Edited by MARS_999

Share this post


Link to post
Share on other sites
ApochPiQ    23003
You've run afoul of the Diamond Problem.

The problem is straightforward if you understand how class instances are stored in memory. Basically, if A has any member variables or a vtable, you need to store that data in each object. But you're asking the compiler to inherit from it [i]twice[/i] if you don't use virtual inheritance. This means you will have two copies of the vtable pointer [i]and[/i] any member data from A, in your D objects (because you're inheriting from A in two different ways).

But then the question arises: which copy should the compiler actually use? There is no way to qualify this in code, so the simplest solution is to just forbid duplicate base classes. Hence the need for virtual inheritance.

You can get a better feel for the way virtual inheritance works by looking it up, but in a nutshell it says "this copy doesn't get to store any member data or a vtable." Edited by ApochPiQ

Share this post


Link to post
Share on other sites
MARS_999    1627
Ah I see now, makes sense, I was wondering how the compiler was going to know in the vtable how to determine the function to call with that mess....

Will this be fixed in C++11? or will they just keep using virtual in the base classes before you inherit? I just don't see why you would ever want two copies and should just be an implicit behavior to not allow two copies? and if you for whatever reason wanted two, make it explicit to do so....

Share this post


Link to post
Share on other sites
RDragon1    1205
virtual inheritance is so rarely used (don't think I've ever used it) that I don't think it really matters if this gets "fixed". I've never seen a use case for virtual inheritance, so it's good that it needs to be explicitly stated.

You should probably figure out why you need virtual inheritance and fix your design

Share this post


Link to post
Share on other sites
RDragon1    1205
[quote name='ApochPiQ' timestamp='1341173006' post='4954634']
But then the question arises: which copy should the compiler actually use? There is no way to qualify this in code,
[/quote]

actually there is. You have to qualify it with the 'path' to the base type you want, to tell the compiler 'which' A to use:

[CODE]
struct A { int x; }; struct B0 : A {}; struct B1 : A {}; struct C : B0, B1 {};

C c;

c.B0::A::x = 5;

c.B1::A::x = 15;
[/CODE]

Share this post


Link to post
Share on other sites
ApochPiQ    23003
[quote name='rdragon1' timestamp='1341195503' post='4954733']
[quote name='ApochPiQ' timestamp='1341173006' post='4954634']
But then the question arises: which copy should the compiler actually use? There is no way to qualify this in code,
[/quote]

actually there is. You have to qualify it with the 'path' to the base type you want, to tell the compiler 'which' A to use:

[CODE]
struct A { int x; }; struct B0 : A {}; struct B1 : A {}; struct C : B0, B1 {};

C c;

c.B0::A::x = 5;

c.B1::A::x = 15;
[/CODE]
[/quote]

Yes, you can qualify member accesses this way; you can even get around casting problems if you're careful to cast to one branch of the diamond first. But it has other gotchas, like order of constructor/destructor invocation, which are subtle and easy to screw up.


The basic wisdom holds: if you're encountering the diamond problem, your design is probably wrong.

Share this post


Link to post
Share on other sites
Alex Melbourne    294
While explicitly stating which member to use is legal it is ugly and a terrible idea. Not only does it then waste memory by creating multiple copies of the same object but that approach doesn't work when you start sealing off members with access-specifiers.

It works there because you're using a structure.

Share this post


Link to post
Share on other sites
Shannon Barber    1681
If you use 'virtual' then both paths up the diamond get the same instance.
If you don't use 'virtual' then it's not actually a diamond, it's a V and you have two sets of data in your class that happen to have the same name.
You disambiguate which one you want to access by naming which path you want to take up the tree.

Regarding this, pedantically correct C++ code should almost always use virtual inheritance because we almost never want to inherit two copies of the same stuff.
C++, as a language, is broken in this regard because the "correct" place to specific this behavior is in the base-class not in (many) derived classes.

You can work-around this by imposing similar rules that other languages have.
e.g. Use interfaces (pure abstract base classes) and always use virtual inheritance for interfaces.
Then if you do end up with two implementations for a method, the compiler will yell at you and can just override that method in the new class to disambiguate.
You can thusly retain C++'s advantage of mix-in multiple-inheritance and have a clean-exit for "the dreaded diamond". Edited by Shannon Barber

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this