Multiple inheritance and pure virtual functions

11,519

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.

Share:
11,519

Related videos on Youtube

HighCommander4
Author by

HighCommander4

Updated on June 04, 2022

Comments

  • HighCommander4
    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
    HighCommander4 over 12 years
    Is there a way for me to say "use the implementation from implementation_base" for both implementations of foo(), without using virtual inheritance?
  • bdonlan
    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 make interface_base and interface empty wrappers that virtually derive from the real interface class.
  • HighCommander4
    HighCommander4 over 12 years
    Your example is rather different than mine. In my case, implementation derives from interface because it will be used polymorphically as an interface*, and it derives from implementation_base to reuse the implementation of the interface_base portion of interface.
  • Keldon Alleyne
    Keldon Alleyne over 12 years
    Ok, so who needs to see it as an implementation_base? And who communicates with interface but not interface_base? What was the thinking behind implementation_base inheriting from interface_base rather than interface? Although my questions are pretty abstract, are you solving a real system design or just the diamond problem?
  • HighCommander4
    HighCommander4 over 12 years
    No one needs to see it as an implementation_base, I could have just derived it from interface and duplicated the implementation inside implementation_base - but I don't want to duplicate things.
  • Keldon Alleyne
    Keldon Alleyne over 12 years
  • HighCommander4
    HighCommander4 over 12 years
    I'm familiar with private inheritance, but I don't see how it helps me. If I derive implementation from implementation_base privately, then foo() will not be publically accessible to users of implementation.
  • Keldon Alleyne
    Keldon Alleyne over 12 years
    Perhaps 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 writing using interface_base::foo;.
  • curiousguy
    curiousguy almost 12 years
    "If I derive implementation from implementation_base privately" and publicly from interface
  • Paul Groke
    Paul Groke about 9 years
    I 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
    curiousguy about 9 years
    @PaulGroke Hum.. you may be right. Proper idiom? recommended idiom?
  • Paul Groke
    Paul Groke about 9 years
    I 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
    Mikhail almost 7 years
    I'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
    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).