The following code:
struct interface_base
{
virtual void foo() = 0;
};
struct interface : public interface_base
{
virtual void bar() = 0;
};
struct implementation_base : public interface_base
{
void foo();
};
struct implementation : public implementation_base, public interface
{
void bar();
};
int main()
{
implementation x;
}
fails to compile with the following errors:
test.cpp: In function 'int main()':
test.cpp:23:20: error: cannot declare variable 'x' to be of abstract type 'implementation'
test.cpp:16:8: note: because the following virtual functions are pure within 'implementation':
test.cpp:3:18: note: virtual void interface_base::foo()
I have played around with it and figured out that making the 'interface -> interface_base' and 'implementation_base -> interface_base' inheritances virtual, fixes the problem, but I don't understand why. Can someone please explain what is going on?
p.s. I omitted the virtual destructors on purpose to make the code shorter. Please don't tell me to put them in, I already know :)
You have two
interface_base
base classes in your inheritance tree. This means you must provide two implementations offoo()
. And calling either of them will be really awkward, requiring multiple casts to disambiguate. This usually is not what you want.To resolve this, use virtual inheritance:
With virtual inheritance, only one instance of the base class in question is created in the inheritance heirarchy for all virtual mentions. Thus, there's only one
foo()
, which can be satisfied byimplementation_base::foo()
.For more information, see this prior question - the answers provide some nice diagrams to make this all more clear.
For the case of 'solving' the diamond inheritance problem, the solutions offered by bdonlan are valid. Having said that, you can avoid the diamond-problem with design. Why must every instance of a given class be seen as both classes? Are you ever going to pass this same object to a class that says something like:
Surely it would be better in this case for
Icecream
to just implementNutritionalConsumable
and provide aGetAsDrink()
andGetAsFood()
method that will return a proxy, purely for the sake of appearing as either food or drink. Otherwise that suggests that there is a method or object that accepts aFood
but somehow wants to later see it as aDrink
, which can only be achieved with adynamic_cast
, and needn't be the case with a more appropriate design.The usual C++ idiom is:
In this case we would have:
In
implementation
, the uniqueinterface_base
virtual base is :interface
:implementation
--public-->interface
--public-->interface_base
implementation_base
:implementation
--private-->implementation_base
--public-->interface_base
When client code does one of these derived to base conversions:
what matters is only that there is a least one accessible inheritance path from the derived class to the given base class subobject; other inaccessible paths are simply ignored. Because inheritance of the base class is only virtual here, there is only one base class subject so these conversions are never ambiguous.
Here, the conversion from
implementation
tointerface_base
, can always be done by client code viainterface
; the other inaccessible path does not matter at all. The uniqueinterface_base
virtual base is publicly inherited fromimplementation
.In many cases, the implementation classes (
implementation
,implementation_base
) will be kept hidden from client code: only pointers or references to the interface classes (interface
,interface_base
) will be exposed.