theHacker
theHacker

Reputation: 4043

Virtual inheritance: Why does it work when only one base class has "virtual" keyword? Is there a better way?

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

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:

Upvotes: 3

Views: 3551

Answers (2)

dyp
dyp

Reputation: 39121

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>

Upvotes: 7

Anton Savin
Anton Savin

Reputation: 41301

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

Upvotes: 2

Related Questions