Multiple Inheritance and the Diamond Problem in C++
Learn multiple inheritance and the Diamond Problem in C++, including virtual inheritance and practical solutions.
C++ supports multiple inheritance, meaning a class can derive from more than one base class. However, this flexibility introduces complexity — especially when the same base class is inherited multiple times through different paths, creating the diamond problem. In this article, we’ll explore what the diamond problem is, why it occurs, its symptoms, and how it can be solved using virtual inheritance.
1. What Is the Diamond Problem?
Consider the following hierarchy:
Publication
/ \
EBook PrintedBook
\ /
HybridBook
HybridBook inherits from both EBook and PrintedBook.
Since both of these derive from Publication, the HybridBook class ends up
with two separate Publication subobjects.
The result is ambiguity, duplication, and extra memory usage.
2. The Problem (Non-Virtual Inheritance)
Let’s look at the issue in practice:
#include <iostream>
#include <string>
using namespace std;
class Publication {
protected:
string title;
public:
Publication(string t) : title(t) {}
void PrintTitle() const { cout << "Title: " << title << endl; }
};
class EBook : public Publication {
public:
EBook(string t) : Publication(t) {}
};
class PrintedBook : public Publication {
public:
PrintedBook(string t) : Publication(t) {}
};
class HybridBook : public EBook, public PrintedBook {
public:
// Compilation error: Which Publication?
// HybridBook(string t) : EBook(t), PrintedBook(t) {}
HybridBook(string te, string tp) : EBook(te), PrintedBook(tp) {}
};
int main() {
HybridBook hb("E-Title", "P-Title");
// Ambiguity: two different Publication subobjects exist
// hb.PrintTitle(); // ERROR: 'PrintTitle' is ambiguous
// Disambiguation:
hb.EBook::PrintTitle(); // Title: E-Title
hb.PrintedBook::PrintTitle(); // Title: P-Title
}
As you can see, HybridBook contains two distinct Publication subobjects.
This leads to duplication and ambiguity in method calls and shared data members.
3. The Solution: Virtual Inheritance
If both intermediate classes (EBook and PrintedBook) inherit from the same base class
virtually, the lowest class (HybridBook) will contain only a
single shared copy of the base class.
class Publication {
protected:
string title;
public:
Publication(string t) : title(t) { cout << "Publication()" << endl; }
void PrintTitle() const { cout << "Title: " << title << endl; }
};
// NOTE: virtual public inheritance
class EBook : virtual public Publication {
public:
EBook(string t) : Publication(t) { cout << "EBook()" << endl; }
};
class PrintedBook : virtual public Publication {
public:
PrintedBook(string t) : Publication(t) { cout << "PrintedBook()" << endl; }
};
class HybridBook : public EBook, public PrintedBook {
public:
// The most derived class is responsible for calling the virtual base constructor
HybridBook(string t)
: Publication(t), EBook(t), PrintedBook(t) {
cout << "HybridBook()" << endl;
}
};
Now HybridBook contains only one Publication subobject,
removing ambiguity completely:
int main() {
HybridBook hb("Unified Title");
hb.PrintTitle(); // Now called through a single Publication instance
}
Important: The most derived class (HybridBook)
is always responsible for initializing the virtual base class.
4. Constructor Order and Virtual Bases
When virtual inheritance is used, constructor calls occur in the following order:
- Virtual base classes are constructed first (only one instance).
- Then the intermediate classes (
EBook,PrintedBook). - Finally, the most derived class (
HybridBook).
Additionally, only the most derived class initializes the virtual base:
class Publication {
public:
string title;
Publication(string t) : title(t) { cout << "Publication()" << endl; }
};
class EBook : virtual public Publication {
public:
// Does not initialize Publication (virtual base handled by the most derived class)
EBook() : Publication("dummy-ignored") { cout << "EBook()" << endl; }
};
class PrintedBook : virtual public Publication {
public:
PrintedBook() : Publication("dummy-ignored") { cout << "PrintedBook()" << endl; }
};
class HybridBook : public EBook, public PrintedBook {
public:
HybridBook(string t) : Publication(t), EBook(), PrintedBook() {
cout << "HybridBook()" << endl;
}
};
int main() {
HybridBook hb("Unified");
cout << hb.title << endl; // "Unified"
}
5. Ambiguity and Resolution Strategies
Even with virtual inheritance, methods with the same name can still cause ambiguity. Possible solutions include:
- Scope qualification: e.g.
obj.EBook::Foo(). - Override: Define a unified behavior in the derived class.
- using declaration: Bring the desired method from a base class into scope.
struct A { void Info() const { cout << "A" << endl; } };
struct B : virtual A { void Info() const { cout << "B" << endl; } };
struct C : virtual A { void Info() const { cout << "C" << endl; } };
struct D : B, C {
// Resolve ambiguity with a single override
void Info() const { cout << "D (unified)" << endl; }
};
6. Example: Hybrid Book Ecosystem
A more realistic scenario:
BookBase is an abstract base class;
EBook and PrintedBook inherit it virtually;
HybridBook combines both pricing models.
#include <iostream>
#include <string>
using namespace std;
class BookBase {
protected:
string title; string author;
public:
BookBase(string t, string a) : title(t), author(a) {}
virtual ~BookBase() = default;
virtual double GetBasePrice() const = 0;
virtual void Print() const {
cout << title << " - " << author << endl;
}
};
class EBook : virtual public BookBase {
protected:
double digitalPrice; double vat;
public:
EBook(string t, string a, double price, double v)
: BookBase(t,a), digitalPrice(price), vat(v) {}
double GetBasePrice() const override { return digitalPrice*(1+vat); }
};
class PrintedBook : virtual public BookBase {
protected:
double printBase; int pages; bool hardCover;
public:
PrintedBook(string t, string a, double base, int p, bool hard)
: BookBase(t,a), printBase(base), pages(p), hardCover(hard) {}
double GetBasePrice() const override {
double cost = printBase + pages*0.03;
if (hardCover) cost *= 1.1;
return cost;
}
};
class HybridBook : public EBook, public PrintedBook {
public:
HybridBook(string t, string a,
double digital, double vat,
double printBase, int p, bool hard)
: BookBase(t,a), // virtual base initialized here
EBook(t,a,digital,vat),
PrintedBook(t,a,printBase,p,hard) {}
double GetFinalPrice() const {
// Single BookBase instance; combine digital & print cost
return 0.6*EBook::GetBasePrice() + 0.4*PrintedBook::GetBasePrice();
}
void Print() const override {
BookBase::Print();
cout << "Final Price: " << GetFinalPrice() << " €" << endl;
}
};
int main() {
HybridBook hb("Modern C++", "A. Dev",
100.0, 0.1, 50.0, 400, true);
hb.Print();
}
7. Design Notes and Alternatives
- Multiple inheritance is powerful but complex. Prefer composition (has-a) when possible.
- For behavior sharing, pure virtual interfaces (like abstract classes) are often simpler.
- When using virtual inheritance, remember that the most derived class initializes the virtual base.
- To reduce ambiguity, use override, using, or explicit scope resolution.
8. TL;DR
- Diamond problem: The same base class inherited twice → duplicate copies, ambiguity, and redundancy.
- Virtual inheritance ensures only one shared base instance, solving ambiguity.
- The most derived class is responsible for calling the virtual base constructor.
- Resolve ambiguity using qualification (
A::Foo), override, or using. - Prefer composition or abstract interfaces over multiple inheritance when possible.
- All examples are compatible with Visual Studio 2022 and GCC 11+.