Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Resolving Multiple Inheritance Ambiguity
Avoid diamond inheritance problems with virtual base classes.
Virtual base classes
Advanced note: This lesson covers virtual base classes and the diamond problem in multiple inheritance. This section is an advanced topic and can be skipped or skimmed if desired.
In the lesson on multiple inheritance, we discussed the "diamond problem". In this section, we will resume this discussion.
The diamond problem
Here is our example from the previous lesson (with some constructors) illustrating the diamond problem:
#include <iostream>
class Hardware
{
public:
Hardware(int power)
{
std::cout << "Hardware: " << power << '\n';
}
};
class Keyboard: public Hardware
{
public:
Keyboard(int keys, int power)
: Hardware{ power }
{
std::cout << "Keyboard: " << keys << '\n';
}
};
class Mouse: public Hardware
{
public:
Mouse(int buttons, int power)
: Hardware{ power }
{
std::cout << "Mouse: " << buttons << '\n';
}
};
class Computer: public Keyboard, public Mouse
{
public:
Computer(int keys, int buttons, int power)
: Keyboard{ keys, power }, Mouse{ buttons, power }
{
}
};
Although you might expect to get an inheritance diagram that looks like a diamond shape, if you were to create a Computer class object, by default you would end up with two copies of the Hardware class -- one from Mouse, and one from Keyboard. This has a hierarchical structure where Hardware appears twice.
We can create a short example that will show this in action:
int main()
{
Computer computer{ 104, 3, 500 };
return 0;
}
This produces the result:
Hardware: 500 Keyboard: 104 Hardware: 500 Mouse: 3
As you can see, Hardware got constructed twice.
While this is often desired, other times you may want only one copy of Hardware to be shared by both Keyboard and Mouse.
Virtual base classes
To share a base class, simply insert the "virtual" keyword in the inheritance list of the derived class. This creates what is called a virtual base class, which means there is only one base object. The base object is shared between all objects in the inheritance tree and it is only constructed once. Here is an example (without constructors for simplicity) showing how to use the virtual keyword to create a shared base class:
class Hardware
{
};
class Keyboard: virtual public Hardware
{
};
class Mouse: virtual public Hardware
{
};
class Computer: public Keyboard, public Mouse
{
};
Now, when you create a Computer class object, you will get only one copy of Hardware per Computer that will be shared by both Keyboard and Mouse.
However, this leads to one more problem: if Keyboard and Mouse share a Hardware base class, who is responsible for creating it? The answer, as it turns out, is Computer. The Computer constructor is responsible for creating Hardware. Consequently, this is one time when Computer is allowed to call a non-immediate-parent constructor directly:
#include <iostream>
class Hardware
{
public:
Hardware(int power)
{
std::cout << "Hardware: " << power << '\n';
}
};
class Keyboard: virtual public Hardware // note: Hardware is now a virtual base class
{
public:
Keyboard(int keys, int power)
: Hardware{ power } // this line is required to create Keyboard objects, but ignored in this case
{
std::cout << "Keyboard: " << keys << '\n';
}
};
class Mouse: virtual public Hardware // note: Hardware is now a virtual base class
{
public:
Mouse(int buttons, int power)
: Hardware{ power } // this line is required to create Mouse objects, but ignored in this case
{
std::cout << "Mouse: " << buttons << '\n';
}
};
class Computer: public Keyboard, public Mouse
{
public:
Computer(int keys, int buttons, int power)
: Hardware{ power }, // Hardware is constructed here
Keyboard{ keys, power }, Mouse{ buttons, power }
{
}
};
This time, our previous example:
int main()
{
Computer computer{ 104, 3, 500 };
return 0;
}
produces the result:
Hardware: 500 Keyboard: 104 Mouse: 3
As you can see, Hardware only gets constructed once.
There are a few details that we would be remiss if we did not mention.
First, for the constructor of the most derived class, virtual base classes are always created before non-virtual base classes, which ensures all bases get created before their derived classes.
Second, note that the Keyboard and Mouse constructors still have calls to the Hardware constructor. When creating an instance of Computer, these constructor calls are simply ignored because Computer is responsible for creating the Hardware, not Keyboard or Mouse. However, if we were to create an instance of Keyboard or Mouse, those constructor calls would be used, and normal inheritance rules apply.
Third, if a class inherits one or more classes that have virtual parents, the most derived class is responsible for constructing the virtual base class. In this case, Computer inherits Mouse and Keyboard, both of which have a Hardware virtual base class. Computer, the most derived class, is responsible for creation of Hardware. Note that this is true even in a single inheritance case: if Computer singly inherited from Mouse, and Mouse was virtually inherited from Hardware, Computer is still responsible for creating Hardware.
Fourth, all classes inheriting a virtual base class will have a virtual table, even if they would normally not have one otherwise, and thus instances of the class will be larger by a pointer.
Because Keyboard and Mouse derive virtually from Hardware, Computer will only be one Hardware subobject. Keyboard and Mouse both need to know how to find that single Hardware subobject, so they can access its members (because after all, they are derived from it). This is typically done through some virtual table magic (which essentially stores the offset from each subclass to the Hardware subobject).
Summary
The diamond problem: In multiple inheritance, when two classes inherit from a common base class, and a fourth class inherits from both of those classes, by default the fourth class ends up with two copies of the base class. This creates a diamond-shaped inheritance hierarchy where the base class appears twice.
Virtual base classes: To share a base class in a multiple inheritance hierarchy, use the virtual keyword in the inheritance list of the derived classes (e.g., class Keyboard: virtual public Hardware). This creates a virtual base class, meaning there is only one instance of the base object shared between all objects in the inheritance tree.
Virtual base construction: When using virtual base classes, the most derived class is responsible for constructing the virtual base class directly, even if it's not an immediate parent. The most derived class constructor must explicitly call the virtual base class constructor.
Ignored constructor calls: When creating an instance of the most derived class, constructor calls to the virtual base class in intermediate classes are ignored, since the most derived class handles construction. However, these constructor calls are still required for creating instances of the intermediate classes themselves.
Construction order: For the constructor of the most derived class, virtual base classes are always created before non-virtual base classes, which ensures all bases get created before their derived classes.
Performance implications: All classes inheriting a virtual base class will have a virtual table, even if they would normally not have one otherwise. This makes instances of the class larger by a pointer, as the virtual table pointer is used to store the offset from each subclass to the virtual base subobject.
When to use virtual bases: Use virtual base classes when you want only one copy of a base class to be shared among multiple paths in an inheritance hierarchy. This is particularly useful when modeling real-world relationships where a single shared object makes logical sense.
Virtual base classes provide a solution to the diamond problem in multiple inheritance by ensuring only one instance of the base class exists, though at the cost of additional complexity in construction and a small performance overhead. While this feature is not commonly used, it's important to understand for situations where multiple inheritance creates ambiguous base class instances.
Resolving Multiple Inheritance Ambiguity - Quiz
Test your understanding of the lesson.
Practice Exercises
Virtual Base Classes and Diamond Problem
Solve the diamond problem in multiple inheritance using virtual base classes. Understand how virtual inheritance ensures only one instance of the base class exists.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!