virtual keyword in derived classes and multiple inheritance

Started by
7 comments, last by Shannon Barber 11 years, 9 months ago
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?


//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 that spits out the error


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";
}
};
Advertisement
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 twice if you don't use virtual inheritance. This means you will have two copies of the vtable pointer and 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."

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

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

But then the question arises: which copy should the compiler actually use? There is no way to qualify this in code,


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:


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;
rdragon1 confirmed works fine... odd syntax c.class::class I would have guessed c::class::class

Thanks!

[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,


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:


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;

[/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.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

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.
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".
- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara

This topic is closed to new replies.

Advertisement