Can we use only one virtual inheritance in diamond inheritance pattern?

Viewed 120

Suppose I have some classes: Base, Derived1, Derived2, ...etc. All the derived classes are derived from Base. Now, I want to add some functionality for all derived classes, but these classes can not be modified. So I came up with the following approach:

Create a class Additional using virtual inheritance like this:

class Additional: virtual public Base {
    void f() {
        //some codes operating on class Base
    }
};

Then I can extend each derived class in this way:

class MyDerived1: public Derived1, public Additional {};

However, when I search the Internet, it seems that in diamond inheritance case, we must use virtual inheritance for both classes Derived1 and Additional. As a result, the above approach will not work. So my questions are:

  • Why must we use two virtual inheritance for diamond inheritance case?
  • If my approach does not work, is there another approach that can solve the above difficulty?

Thank you very much!

3 Answers

Why must we use two virtual inheritance for diamond inheritance case?

Virtual inheritance is a contract that a class has to enter into willingly. The class is basically saying that it's willing to share its base sub-object with an instance of another deriving class. It affects object layout and access to the base in the class code. You can't change those things retroactively, especially if the class implementation is compiled and only given to you as an object file.

If my approach does not work, is there another approach that can solve the above difficulty?

Yes, use a template.

template<class BaseT>
class Additional: public BaseT {
    void f() {
        //some codes operating on class Base
    }
};

using MyDerived1 = Additional<Derived1>;

Now, I want to add some functionality for all derived classes

You can write free functions

void some_functionality(Base & base)
{
    ...
}

Why must we use two virtual inheritance for diamond inheritance case?

There's really two questions here. Why do we need to use virtual inheritance at all?

  • Virtual inheritence has a runtime cost. Imagine you are converting a pointer or reference to Derived1 into a pointer or reference to Base. With normal inhertence that just requires adding a fixed numerical offset that is known at compile time (often that offset is zero so there is no cost at all!). With virtual inheritence you have to go through some sort of pointer indirection. This conversion happens in places where it might not be obvious e.g. whenever you call a method defined on the base class you are doing this implicitly. Most classes don't make any use of this virtual inheritence so it doesn't make sense for them all to pay the cost.
  • If you inherit from both Derived1 and Derived2 without virtual inheritence on Base then you end up with two subobjects of type Base. Sometimes this is actually what you want! If C++ always gave you virtual inheritence, even if you didn't ask for it, then you would lose this capability.

The other question is: why do we need "virtual" in both places? In other words, why can't we override a decision in one bit of code to use non-virtual inheritence when we make another class later?

  • Partly this is because of how compilation works in C++. If Derived1 inherits virtually from Base then that affects all Derived1 objects, even the ones that aren't part of Additional. (It has to be that way because a function taking a reference to a Derived1 doesn't know whether it's part of an Additional or not.) You're suggesting that your definition of Additional reaches back into the definition of Derived1 and changes its meaning to be virtual inheritence - even in those compilation units that don't include your header file!
  • Partly, again, this is because you might want two subobject. Say you had D1, D2 and D3, where D1 inherits from Base non-virtually, while D2 and D3 inherit from Base virtually. Then you'd end up with two Base subobjects: one for D1 and another shared between D2 and D3. This is admittedly quite unusual, but it's possible that's what you want, and your proposal would prevent it.
Related