Multiple inheritance and pure virtual functions
Solution 1
You have two interface_base
base classes in your inheritance tree. This means you must provide two implementations of foo()
. 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:
struct interface_base
{
virtual void foo() = 0;
};
struct interface : virtual public interface_base
{
virtual void bar() = 0;
};
struct implementation_base : virtual public interface_base
{
void foo();
};
struct implementation : public implementation_base, virtual public interface
{
void bar();
};
int main()
{
implementation x;
}
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 by implementation_base::foo()
.
For more information, see this prior question - the answers provide some nice diagrams to make this all more clear.
Solution 2
The usual C++ idiom is:
- public virtual inheritance for interface classes
- private non-virtual inheritance for implementation classes
In this case we would have:
struct interface_base
{
virtual void foo() = 0;
};
struct interface : virtual public interface_base
{
virtual void bar() = 0;
};
struct implementation_base : virtual public interface_base
{
void foo();
};
struct implementation : private implementation_base,
virtual public interface
{
void bar();
};
In implementation
, the unique interface_base
virtual base is :
- publicly inherited via
interface
:implementation
--public-->interface
--public-->interface_base
- privately inherited via
implementation_base
:implementation
--private-->implementation_base
--public-->interface_base
When client code does one of these derived to base conversions:
- derived to base pointer conversions,
- reference binding of base type with an initializer of static type derived,
- access to inherited base class members via a lvalue of derived static type,
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
to interface_base
, can always be done by client code via interface
; the other inaccessible path does not matter at all. The unique interface_base
virtual base is publicly inherited from implementation
.
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.
Solution 3
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:
void ConsumeFood(Food *food);
void ConsumeDrink(Drink *drink);
class NutritionalConsumable {
float calories() = 0;
float GetNutritionalValue(NUTRITION_ID nutrition) = 0;
};
class Drink : public NutritionalConsumable {
void Sip() = 0;
};
class Food : public NutritionalConsumable {
void Chew() = 0;
};
class Icecream : public Drink, virtual public Food {};
void ConsumeNutrition(NutritionalConsumable *consumable) {
ConsumeFood(dynamic_cast<Food*>(food));
ConsumeDrink(dynamic_cast<Drink*>(drink));
}
// Or moreso
void ConsumeIcecream(Icecream *icecream) {
ConsumeDrink(icecream);
ConsumeFood(icecream);
}
Surely it would be better in this case for Icecream
to just implement NutritionalConsumable
and provide a GetAsDrink()
and GetAsFood()
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 a Food
but somehow wants to later see it as a Drink
, which can only be achieved with a dynamic_cast
, and needn't be the case with a more appropriate design.
Related videos on Youtube
HighCommander4
Updated on June 04, 2022Comments
-
HighCommander4 about 2 years
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 :)
-
HighCommander4 over 12 yearsIs there a way for me to say "use the implementation from
implementation_base
" for both implementations offoo()
, without using virtual inheritance? -
bdonlan over 12 years@HighCommander4, you could derive interface from
implementation_base
or vice versa, thus eliminating the diamond and creating a linear inheritance tree. Other than that, no, but if you want to hide it, you could makeinterface_base
andinterface
empty wrappers that virtually derive from the real interface class. -
HighCommander4 over 12 yearsYour example is rather different than mine. In my case,
implementation
derives frominterface
because it will be used polymorphically as aninterface*
, and it derives fromimplementation_base
to reuse the implementation of theinterface_base
portion ofinterface
. -
Keldon Alleyne over 12 yearsOk, so who needs to see it as an
implementation_base
? And who communicates withinterface
but notinterface_base
? What was the thinking behindimplementation_base
inheriting frominterface_base
rather thaninterface
? Although my questions are pretty abstract, are you solving a real system design or just the diamond problem? -
HighCommander4 over 12 yearsNo one needs to see it as an
implementation_base
, I could have just derived it frominterface
and duplicated the implementation insideimplementation_base
- but I don't want to duplicate things. -
Keldon Alleyne over 12 years
-
HighCommander4 over 12 yearsI'm familiar with private inheritance, but I don't see how it helps me. If I derive
implementation
fromimplementation_base
privately, thenfoo()
will not be publically accessible to users ofimplementation
. -
Keldon Alleyne over 12 yearsPerhaps I should have written it as, "See C++ composition vs private inheritance". The article / tutorial not only discusses where and when to use it, it also quotes a fundamental statement from Scott Meyers, as well as showing you how to make
foo()
publicly available by writingusing interface_base::foo;
. -
curiousguy almost 12 years"If I derive implementation from implementation_base privately" and publicly from
interface
-
Paul Groke about 9 yearsI wouldn't say that "public virtual inheritance for interface classes" is "the (or a) usual C++ idiom". Maybe it should be, but from what I can tell it surely isn't.
-
curiousguy about 9 years@PaulGroke Hum.. you may be right. Proper idiom? recommended idiom?
-
Paul Groke about 9 yearsI wrote "maybe it should be", because I'm honestly not sure if it should. I almost never use virtual inheritance (in fact I'm not sure if I ever used it in production code). So I don't know the problems it can cause and which of those problems also affect virtual inheritance with pure interface classes (i.e. trivial ctor, trivial dtor, no data).
-
Mikhail almost 7 yearsI'm also interested in this "public virtual inheritance for interface classes" thing. I didn't hear it anywhere before. Though it sounds applicable in most cases (for interfaces). Can you recall any sources? I cannot google anything suitable.
-
curiousguy almost 7 years@Mikhail Mostly The original books (should I say bibles?) by Stroustrup: TC++PL, D&E (read these decades ago, not quite sure).