3

I want to implement a class hierarchy in C++:

  • I need interfaces so I can provide multiple implementations.
  • I need common methods in all the classes. But I need to be able to override specific methods.
  • Constructors all take at least one parameter.

Simplified I have this code:

#include <iostream>

class IClass {
public:
    virtual int commonMethod() const = 0;
};

class Class : public virtual IClass {
protected:
    int commonValue;

public:
    Class(int commonValue) : commonValue(commonValue) {}

    virtual int commonMethod() const {
        return commonValue;
    }
};


class IClassDerived : public virtual IClass {
public:
    virtual void specialMethod() = 0;
};

class ClassDerived : public Class, public virtual IClassDerived {
public:
    ClassDerived(int commonValue) : Class(commonValue) {}

    virtual void specialMethod() {
        // do something
    }
};


class IClassDerived2 : public virtual IClassDerived {
public:
    virtual void specialMethod2() = 0;
};

class ClassDerived2 : public ClassDerived, public virtual IClassDerived2 {
public:
    ClassDerived2(int commonValue) : ClassDerived(commonValue) {}

    virtual void specialMethod2() {
        specialMethod();
    }
};


class IClassDerived3 : public virtual IClassDerived2 {
public:
    virtual int commonMethod() const override = 0;
};

class ClassDerived3 : public ClassDerived2, public virtual IClassDerived3 {
public:
    ClassDerived3(int commonValue) : ClassDerived2(commonValue) {}

    virtual int commonMethod() const override {
        return 4711;
    }
};


int main() {
    ClassDerived foo(1);
    ClassDerived2 foo2(2);
    ClassDerived3 foo3(3);

    std::cout << foo.commonMethod() << " " << foo2.commonMethod() << " " << foo3.commonMethod() << " " << std::endl;
    // 1 2 4711

    return 0;
}

I now have two questions:

  • Why does this work at all?
    • If I try without virtual inheritance I get errors "‘specialMethod’ is ambiguous" and "... because the following virtual functions are pure within ‘ClassDerived’: virtual int IClass::commonMethod() const". Because of the two base classes every member is there two times which results in these errors. Ok.
    • If I do virtual inheritance and specify both base classes with "public virtual" I get "no matching function for call to ‘Class::Class()’". Researching showed me that in case of virtual inheritance I need a default constructor for the base classes.
    • By trial-and-error I found the solution above. But I don't understand why it works so far. What happens when only one base class is "virtual" but not the other one?
  • Is there a better way? I managed to get this little example to compile but in reality my classes are complexer and I am afraid this only works in this little snippet and I am not seeing future problems this could bring up...
theHacker
  • 3,984
  • 1
  • 19
  • 34
  • The nice thing about interfaces is that you *don't* need virtual inheritance. There is nothing to inherit. – Hans Passant Dec 27 '14 at 20:44
  • @HansPassant Are you sure? The OP seems to inherit from "interfaces" via two base clauses; won't you end up with a separate (partion of the) vtable for each one? [Simplified example](http://coliru.stacked-crooked.com/a/2e7bbe1c60ceb55f) – dyp Dec 27 '14 at 21:11

2 Answers2

7

I have recently found a workaround that does not need virtual inheritance (see below).

Basically, the language requires the use of virtual inheritance in this case to solve the problem directly since you inherit multiple times from the same class. Without virtual inheritance, you'll end up with this:

Interface0  Interface0  Interface0
    ^           ^           ^______
    |           |                  \
Interface1  Interface1              Impl0
    ^           ^__________________   ^
    |                              \  |
Interface2                          Impl1
    ^______________________________   ^
                                   \  |
                                    Impl2

There are multiple "instances" of the InterfaceX base class, which are independent. Consider the Interface0 instance from the path Impl1 -> Interface1 -> Interface0. The Impl0 class does not inherit from that instance of Interface0, hence it does not implement its virtual functions. Note that this is useful if all these interface classes were stateful classes (with data members) instead of pure interfaces.

But in this particular situation, where you only inherit from an interface, virtual inheritance isn't required in theory. We want to get to the following picture:

Interface0 _
    ^     |\
    |       \
Interface1 _ Impl0
    ^     |\   ^
    |       \  |
Interface2 _ Impl1
          |\   ^
            \  |
             Impl2

Theoretically, Impl1 could define one single vtable with the entries from Impl0 implementing the virtual functions from Interface0, and the functions from Impl1 implementing the virtual functions from Interface1. The result would be a single vtable with no offset computations required (hence no need for virtual inheritance).

But, alas, the language doesn't define inheritance this way -- it doesn't make a difference between abstract classes and pure interfaces. It lets you only override virtual functions through sideway-inheritance (Impl0 overriding virtual functions of Impl1 -> Interface1 -> Interface0) if you inherit them via virtual inheritance. By inheriting virtually, you specify that you indeed inherit only once from Interface0, so both paths from Impl1 to Interface0 (direct inheritance and via Impl0) yield the same class.

Virtual inheritance has several drawbacks, since it must allow cases where the location of the base class (relative to a subobject) can only be determined at run-time. However, there's a workaround that doesn't need virtual inheritance.

It is often more useful to write a class first self-contained, and then adapt it to an interface. The interface is typically defined by the surrounding architecture, and hence not necessarily general for that class. By separating the interface from the implementation, you allow reuse of the implementation with a different interface.

If we put this together: We cannot use multiple inheritance to implement virtual functions side-ways, and we want to separate interface from implementation. We end up with: Either, we don't let our implementation derive from anything (this leads to boilerplate code) or we derive linearly, the only inheritance from an interface at the top.

By writing our implementation classes as class templates, we can derive linearly and pass the top interface to be derived from up to the base implementation class:

struct Interface0 {
    virtual void fun0() = 0;
};

struct Interface1 : Interface0 {
    virtual void fun1() = 0;
};

struct Interface2 : Interface1 {
    virtual void fun2() = 0;
};


template<typename Interface = Interface0>
struct Impl0 : Interface {
    void fun0() {}
};

template<typename Interface = Interface1>
struct Impl1 : Impl0<Interface> {
    void fun1() {}
};

template<typename Interface = Interface2>
struct Impl2 : Impl1<Interface> {
    void fun2() {}
};


int main()
{
    auto x = Impl2<Interface2>(); // or simply: Impl2<>()
    Interface2* p = &x;
}

Note that we use inheritance for both: implementing Impl1 in terms of Impl0, and passing the extended interface to our base class.

The case in main, Impl2<>:

Interface0
    ^
    |
Interface1
    ^
    |
Interface2 _
          |\
            \
             Impl0<Interface2>
               ^
               |
             Impl1<Interface2>
               ^
               |
             Impl2<Interface2>

Another case, Impl1<>:

Interface0
    ^
    |
Interface1 _
          |\
            \
             Impl0<Interface1>
               ^
               |
             Impl1<Interface1>
dyp
  • 38,334
  • 13
  • 112
  • 177
  • Another way to solve this is by defining separate interfaces, but I guess that's less convenient and efficient (multiple vtables). E.g. `struct Interface0 { virtual void fun0() = 0; }; struct Interface1 { virtual void fun1() = 0; }; struct Impl0 : Interface0 { void fun0(); }; struct Impl1 : Impl0, Interface1 { void fun1(); }` – dyp Jan 05 '15 at 00:07
  • "_alas, the language doesn't define inheritance this way_" Hopefully, the language doesn't say that non virtual inheritance behaves as virtual inheritance. You have both. – curiousguy Dec 31 '16 at 20:12
  • @curiousguy Sorry, but I don't understand your comment. Do you mean my workaround uses both virtual- and non-virtual inheritance? – dyp Jan 03 '17 at 13:16
  • No, I am pointing out that you can't complain that the semantics of exclusive (non virtual) and shared (virtual) inheritance are different. There is no such thing as a "pure interface". Non virtual inheritance always means non virtual inheritance. Why would you expect non virtual inheritance to sometimes behave as virtual inheritance? – curiousguy Jan 05 '17 at 01:07
  • @curiousguy If there was a concept of a *pure interface* in the language, then I'd like to have special semantics for inheriting from it -- or rather, in order to get these special semantics, one would have to introduce *pure interfaces* into the language. From a theoretical point of view, I agree there's just exclusive and shared inheritance, however the implementations of both are different, and the implementation details leak into the semantics of virtual inheritance (e.g. `static_cast` cannot downcast to a virtually derived class). – dyp Jan 05 '17 at 10:49
  • If you have a pure interface (I presume with only trivial constructors and no data members), I think you are suggesting that it wouldn't matter whether there is only one instance of that almost empty base class subobject in the complete object. Well, it wouldn't matter except for address comparison! – curiousguy Jan 08 '17 at 07:14
  • @curiousguy No, I'm not suggesting that it doesn't matter. As we can see in the OP, it does matter with the current specification of the language. What I'm trying to say is that there's a use-case for an additional kind of inheritance behaviour (in addition to non-virtual and virtual inheritance). – dyp Jan 08 '17 at 11:32
  • It looks like you got the inheritance for Interface1 and Interface2 swapped around in your solution code? – Jeremy W. Murphy Sep 30 '20 at 05:50
  • @JeremyW.Murphy Looks like it indeed. Thanks! – dyp Sep 30 '20 at 10:49
2

You have to initialize virtual base classes in all derived class constructors. Thus, if you had

class ClassDerived : public virtual Class, public virtual IClassDerived {
//                          ^^^^^^^

Then in the constructors of ClassDerived2 and ClassDerived3 you would have to initialize Class despite that it seems to be initialized in the constructor of ClassDerived:

ClassDerived2(int commonValue) : Class(commonValue), ClassDerived(commonValue)  {}
ClassDerived3(int commonValue) : Class(commonValue), ClassDerived2(commonValue) {}

The reason for this is [class.base.init]/7:

A mem-initializer where the mem-initializer-id denotes a virtual base class is ignored during execution of a constructor of any class that is not the most derived class.


Regarding whether there is a better way, personally I'd rather use aggregation and no virtual inheritance, though it requires writing some boilerplate forwarding code:

class IClass {
public:
    virtual int commonMethod() const = 0;
};

class Class : public IClass {
protected:
    int commonValue;

public:
    Class(int commonValue) : commonValue(commonValue) {}

    virtual int commonMethod() const {
        return commonValue;
    }
};


class IClassDerived : public IClass {
public:
    virtual void specialMethod() = 0;
};

class ClassDerived : public IClassDerived { // no inheritance from Class
public:
    ClassDerived(int commonValue) : m_class(commonValue) {}

    virtual int commonMethod() const {
        return m_class.commonMethod();
    }

    virtual void specialMethod() {
        // do something
    }

private:
    Class m_class;
};

// and so on
Anton Savin
  • 40,838
  • 8
  • 54
  • 90