This time I want to talk about virtual inheritance in C ++, and why it should be used very carefully. Previous articles: Part
N1 ,
N2 ,
N3 .
The article is based on the note "
Rake 2: Virtual Inheritance ." The article is good, but, in my opinion, somewhat blurred, and a newcomer may not fully grasp the essence of the dangers. I decided to offer my version of the description of the problems associated with virtual inheritance.
About initializing virtual base classes
At the beginning, let's talk about how classes are allocated in memory, if there is no virtual inheritance. Consider the code:
class Base { ... }; class X : public Base { ... }; class Y : public Base { ... }; class XY : public X, public Y { ... };
Everything is simple here. The members of the non-virtual base class 'Base' are placed as simple data members of the derived class. As a result, inside the object 'XY' we have two independent sub-objects 'Base'. Schematically, this can be represented as:

Figure 1. Non-virtual multiple inheritance.
An object of a virtual base class enters an object of a derived class only once. The device of the object 'XY' for the code below is shown in Figure 2.
class Base { ... }; class X : public virtual Base { ... }; class Y : public virtual Base { ... }; class XY : public X, public Y { ... };

Figure 2. Virtual multiple inheritance.
The memory for the shared subobject 'Base' is likely to be allocated at the end of the 'XY' object. How exactly the class will be arranged depends on the compiler. For example, the classes 'X' and 'Y' may store pointers to the common 'Base' object. But as I understand it, this method is out of use. More often, a reference to a shared sub-object is implemented as an offset or information stored in a virtual function table.
')
Only the "most derived" class 'XY' knows exactly where the memory should be for a sub-object of the virtual base class 'Base'. Therefore, it is up to the derived class itself to initialize all the sub-objects of the virtual base classes.
The 'XY' constructors initialize the 'Base' sub-object and pointers to this object in 'X' and 'Y'. Then the remaining members of the classes 'X', 'Y', 'XY' are initialized.
After the base subobject is initialized in the 'XY' constructor, it will not be initialized again with the 'X' or 'Y' constructor. How this is done depends on the compiler. For example, the compiler may pass a special additional argument to the 'X' and 'Y' constructors, which will indicate not to initialize the 'Base' class.
And now the most interesting, leading to many misunderstandings and errors. Consider the following constructors:
X::X(int A) : Base(A) {} Y::Y(int A) : Base(A) {} XY::XY() : X(3), Y(6) {}
What number will the base class constructor take as an argument? The number 3 or 6? None of them.
The 'XY' constructor initializes the 'Base' virtual subobject, but does so implicitly. The 'Base' constructor is called by default.
When the 'XY' constructor calls the 'X' or 'Y' constructor, it does not initialize 'Base' again. Therefore, an explicit reference to 'Base' with some kind of argument does not occur.
On this adventure with virtual base classes do not end there. In addition to constructors, there are assignment operators. If I'm not mistaken, the standard says that the compiler-generated assignment operator can perform multiple assignments to a sub-object of a virtual base class. And maybe only once. So it is not known how many times the base object will be copied.
If you implement your assignment operator, you must take care of copying the 'Base' object once. Consider the wrong code:
XY &XY::operator =(const XY &src) { if (this != &src) { X::operator =(*this); Y::operator =(*this); .... } return *this; }
This code will duplicate the 'Base' object. To avoid this, the classes 'X' and 'Y' need to implement functions that will not copy the members of the class 'Base'. The contents of the 'Base' class are copied once here. Corrected code:
XY &XY::operator =(const XY &src) { if (this != &src) { Base::operator =(*this); X::PartialAssign(*this); Y::PartialAssign(*this); .... } return *this; }
Such code will work, but all this is ugly and confusing. Therefore, it is said that it is better to avoid multiple virtual inheritance.
Virtual Base Classes and Type Casting
Due to the nature of the placement of virtual base classes in memory, it is impossible to perform such type conversions:
Base *b = Get(); XY *q = static_cast<XY *>(b);
However, an assertive programmer can still type in using the 'reinterpret_cast' operator:
XY *e = reinterpret_cast<XY *>(b);
However, most likely it will give an unusable result. The address of the beginning of the 'Base' object will be interpreted as the beginning of the 'XY' object. And this is not what is necessary. See Figure 3 for an explanation.
The only way to do a cast is to use the dynamic_cast operator. However, the code where dynamic_cast is regularly used, smells bad.

Figure 3. Type casting.
Should virtual inheritance be abandoned?
I agree with the opinion of many authors that virtual inheritance should be avoided in every way. And from simple multiple inheritance is also better to leave.
Virtual inheritance causes problems when initializing and copying objects. Initialization and copying should be handled by the “most derived” class. And that means, he should know intimate details about the device base classes. There is an extra connection between classes, which complicates the structure of the project and makes it necessary to make additional edits in different classes during refactoring. All this contributes to errors and complicates the understanding of the project by new developers.
The complexity of type conversions also contributes to errors. Part of the problem is solved by using the dynamic_cast operator. However, it is a very slow operator. And if it starts to appear en masse in the program, then, most likely, this indicates a poor project architecture. Almost always, you can implement the project structure without resorting to multiple inheritance. Actually, in many languages ​​there are no such frills at all. And this does not interfere with the implementation of large projects.
It is foolish to insist on abandoning virtual inheritance. Sometimes it is useful and convenient. However, it is worthwhile to think carefully, before heaping up complex classes. It is always better to grow a forest of small classes with a shallow hierarchy than to work with several huge trees. For example, often instead of multiple inheritance, you can use aggregation.
The benefits of multiple inheritance
Well, the criticism of multiple virtual inheritance and just multiple inheritance is understandable. Are there any places where it is safe and convenient.
Yes, I can name at least one thing: mixing up the interfaces. If this methodology is not familiar to you, I suggest turning to the book “A rope of sufficient length to ... shoot myself in the foot” [3].
There is no data in the interface class. All functions, as a rule, are purely virtual. There is no constructor in it, or it does nothing. This means that there are no problems with creating or copying such classes.
If the base class is an interface, then the assignment is an empty operation. So even if the object will be copied many times, it is not scary. In the compiled program code, this copying will simply be absent.
additional literature
- Stefan C. Dewhurst. Slippery C ++ locations. How to avoid problems when designing and compiling your programs. - M .: DMK Press. - 264 pp., Ill. BBK 32.973.26-018.2, ISBN 978-5-94074-837-3. (See tip 45 and 53).
- Wikipedia. Aggregation (programming) .
- Alain I. Golub. "A rope of sufficient length to ... shoot yourself in the foot." (It is easily searched on the Internet. You should look at section 101 and on).