An article on how multiple inheritance complicates things. As virtual inheritance, at first glance, it is implemented illogical. As a second glance, logic appears, but the level of complexity and entanglement continues to grow. In general, the more complex the task, the simpler the tools to pick up.
Everything is based on real events, but the examples were simplified as much as possible so that only the essence of the problem remained in them.
So, in the developed application a large number of uniformly processed entities were used. Moreover, some of them were displayable, others needed to be constantly updated, and others included in themselves both. Accordingly, there was a desire to implement the three base classes.
- Renderable : contains the visibility feature and drawing method
- Updatable : contains an indication of activity and state update method
- VisualActivity = Renderable + Updatable
I will add two more artificial classes to demonstrate the difficulties
- JustVisible : just a visible object
- JustVisiblePlusVisualActivity : JustVisible with an updated state
It turns out the following picture

The problem is immediately visible - the final class inherits the Renderable twice: as the parent of JustVisible and VisualActivity. This does not work normally with lists of displayed objects.
')
JustVisiblePlusUpdate* object = new JustVisiblePlusUpdate; std::vector<Renderable*> vector_visible; vector_visible.push_back(object);
Ambiguous conversions are obtained - the compiler cannot understand what legacy is used for which
Renderable branch. He can be helped by specifying the direction by explicitly casting the type to one of the intermediate
JustVisiblePlusUpdate* object = new JustVisiblePlusUpdate; std::vector<Renderable*> vector_visible; vector_visible.push_back(static_cast<VisualActivity*>(object));
Compilation will be successful, but the error will remain. In our case, the same Renderable was required, regardless of how it was inherited. The fact is that in the case of normal inheritance, the descendant class (JustVisiblePlusVisualActivity) contains a separate instance of the parent class for each branch.

Moreover, the properties of each of them can be changed independently. Expressed in c ++, the expression is true
(&static_cast<VisualActivity*>(object)->mVisible) != (&static_cast<JustVisible*>(object)->mVisible)
So the usual multiple inheritance for the problem is not suitable. But the virtual one looked like the same silver bullet that was needed ... All that was needed was to inherit the base classes
Renderable and
Updatable virtually, and the rest in the usual way:
class VisualActivity : public virtual Updatable, public virtual Renderable ... class JustVisible : public virtual Renderable ... class JustVisiblePlusUpdate : public JustVisible, public VisualActivity
All virtually inherited classes are presented in the descendant only once. And everything would work if the base classes had no constructors with parameters. But such designers existed, and there was a surprise. Each virtually inherited class had both a default constructor and a parameterized
class Updatable { public: Updatable() : mActive(true) { } Updatable(bool active) : mActive(active) { }
class Renderable { public: Renderable() : mVisible(true) { } Renderable(bool visible) : mVisible(visible) { }
Descendant classes contained only constructors with parameters.
class VisualActivity : public virtual Updatable, public virtual Renderable { public: VisualActivity(bool visible, bool active) : Renderable(visible) , Updatable(active) { }
class JustVisible : public virtual Renderable { public: JustVisible(bool visible) : Renderable(visible) { }
class JustVisiblePlusUpdate : public JustVisible, public VisualActivity { public: JustVisiblePlusUpdate(bool visible, bool active) : JustVisible(visible) , VisualActivity(visible, active) { }
Still, when creating an object
JustVisiblePlusUpdate* object = new JustVisiblePlusUpdate(false, false);
Renderable default constructor was
invoked ! At first glance, it seemed something wild. But let's take a closer look at where the assumption came from that the above code should lead to a call to the constructor
Renderable :: Renderable (bool visible) instead of
Renderable :: Renderable () .

The problem of assuming that the
Renderable is miraculously divided between
JustVisible ,
VisualActivity and
JustVisiblePlusUpdate has created a problem . But the "miracle" was not to happen. After all, then it would be possible to write something like
class JustVisiblePlusUpdate : public JustVisible, public VisualActivity { public: JustVisiblePlusUpdate(bool active) : JustVisible(true) , VisualActivity(false, active) { }
telling the compiler conflicting information when at the same time
rendering a Renderable with the parameters
true and
false would be required. Nobody wanted to open the possibility for such paradoxes, respectively, and the mechanism works in a different way. In our case, the
Renderable class is no longer part of
JustVisible or
VisualActivity , but belongs directly to
JustVisiblePlusUpdate .

This explains why the default constructor was called — the constructors of the virtual classes should be called by the final heirs, i.e. the working variant would be something like
class JustVisiblePlusUpdate : public JustVisible, public VisualActivity { public: JustVisiblePlusUpdate(bool visible, bool active) : JustVisible(visible) , VisualActivity(visible, active) , Renderable(visible) , Updatable(active) { }
In the case of virtual inheritance, it is necessary, except for the constructors of direct parents, to explicitly call the constructors of all virtually inherited classes. This is not very obvious and can easily be missed in a non-trivial project. So the truth has been confirmed:
no more than one open inheritance for each class . It is not worth it. In our case, it was decided to abandon the separation of
Renderable and
Updatable , limiting it to one basic
VisualActivity . This added some redundancy, but dramatically simplified the overall architecture — keeping track of and maintaining all virtual and ordinary inheritance cases was too expensive.