We have seen the diamond problem with multiple inheritance where two base class copies are created being inherited by intermediary base classes in the inheritance hierarchy.
For example:
#include <iostream>
class Base
{
int m_base1_value{10};
public:
Base(int value)
: m_base1_value{value}
{
std::cout << "Base class constructor" << '\n';
}
};
class Derived : public Base
{
int m_derived_value{};
public:
Derived(int derived_value, int base_value)
: Base{base_value},
m_derived_value{derived_value}
{
}
};
class Derived2 : public Base
{
int m_derived2_value{};
public:
Derived2(int derived_value, int value)
: Base{value},
m_derived2_value{derived_value}
{
}
};
class FinalDerived : public Derived, public Derived2
{
int m_final_value{};
public:
FinalDerived(int final_value, int derived1_value, int derived2_value, int base_value)
: m_final_value{final_value},
Derived{derived1_value, base_value},
Derived2{derived2_value, base_value}
{
std::cout << "Final class constructor" << '\n';
}
};
int main(int argc, char const *argv[])
{
FinalDerived d{1, 2, 3, 4}; // creates base class two times
return 0;
}If we run this program, we should see the line that Base class constructor prints, two times.
Base class constructor
Base class constructor
Final class constructor
We can solve this problem using virtual base class. We can do that by using virtual keyword while inheriting the class:
#include <iostream>
class Base
{
int m_base1_value{10};
public:
Base(int value)
: m_base1_value{value}
{
std::cout << "Base class constructor" << '\n';
}
int getValue()
{
return m_base1_value;
}
};
class Derived : virtual public Base
{
int m_derived_value{};
public:
Derived(int derived_value, int base_value)
: Base{base_value},
m_derived_value{derived_value}
{
// std::cout << "Derived class constructor" << '\n';
}
};
class Derived2 : virtual public Base
{
int m_derived2_value{};
public:
Derived2(int derived_value, int value)
: Base{value},
m_derived2_value{derived_value}
{
// std::cout << "Derived2 class constructor" << '\n';
}
};
class FinalDerived : public Derived, public Derived2
{
int m_final_value{};
public:
FinalDerived(int final_value, int derived1_value, int derived2_value, int base_value)
: m_final_value{final_value},
Base{base_value},
Derived{derived1_value, base_value},
Derived2{derived2_value, base_value}
{
std::cout << "Final class constructor" << '\n';
}
};
int main(int argc, char const *argv[])
{
FinalDerived d{1, 2, 3, 4}; // creates base class two times
return 0;
}When we run this program, we should not see the Base class log two times, i.e. Base class portion should only be created once.
Looking at the code, we made three changes:
- Used
virtualkeyword to inheritBaseforDerivedclass - Used
virtualkeyword to inheritBaseforDerived2class - Called
Baseclass constructor in theFinalDerivedinitializer list of the constructor.
There are a few details to remember:
- Virtual base classes are always created first before the non-virtual base classes. In this case,
Basewould be created first then other base classes such asDerivedandDerived2. - Most derived class has to call the constructor of the virtual base class because it is responsible for the creation of the virtual base class. Although we have called virtual class constructor for other derived base class such as
DerivedandDerived2, they will be ignored. - If any class is inheriting a virtual base class, will have a virtual table, even if they do not have any virtual function. This happens because there is single portion of virtual base class
Basewhich has to be used by other base classes portionsDerivedandDerived2. This virtual table stores offsets to be used to locateBaseportion.
Another example
There is another example showing accessing a member inherited from the most base class in the hierarchy which throws ambiguous compiler error:
#include <iostream>
class Animal
{
public:
int a{1};
};
class Bird : public Animal
{
public:
int b{1};
};
class Mammal : public Animal
{
public:
int c{1};
};
class Bat : public Bird, public Mammal
{
public:
int d{1};
};
int main(int argc, char const *argv[])
{
Bat b;
std::cout << b.a << '\n';
return 0;
}As Animal portions exist separately for Bird and Mammal portions, something like below:
Bat
+--------------------+
| Mammal |
| Animal |
| a |
| c |
+--------------------+
| Bird |
| Animal |
| a |
| b |
+--------------------+
| d |
+--------------------+
So, when accessing b.a, which .a should be accessed as there are two .a available, one through Mammal::a and another through Bird::a.
If we make Animal class as virtual base class, the problem would be solved as there exist only one portion of Animal.