Skip to content
C++ Better Explained
Go back
The Diamond Problem in C++: Multiple Inheritance and virtual Bases

The Diamond Problem in C++: Multiple Inheritance and virtual Bases

C++ lets a class inherit from more than one parent. That flexibility comes with one famous trap, and it has a shape: draw the class hierarchy and you get a diamond.

If you’ve ever seen error: request for member 'x' is ambiguous and had no idea why, this is almost certainly the cause.


Setting Up the Diamond

Start with a base class and two classes that extend it:

#include <iostream>

class Device {
public:
    int serialNumber = 0;
    void powerOn() { std::cout << "Powering on\n"; }
};

class Printer : public Device {
public:
    void print() { std::cout << "Printing\n"; }
};

class Scanner : public Device {
public:
    void scan() { std::cout << "Scanning\n"; }
};

Nothing wrong so far. Now build a device that does both:

class Copier : public Printer, public Scanner {
};

The hierarchy is now a diamond — Device at the top, Printer and Scanner in the middle, Copier at the bottom:

        Device
       /      \
  Printer    Scanner
       \      /
        Copier

Where It Breaks

Try to use the inherited members and the compiler stops you:

int main() {
    Copier c;

    c.print();            // fine
    c.scan();             // fine

    c.powerOn();          // ERROR: request for member 'powerOn' is ambiguous
    c.serialNumber = 42;  // ERROR: request for member 'serialNumber' is ambiguous
}

The reason is memory layout. Printer contains a full Device. Scanner contains a full Device. Copier contains both — so a Copier object holds two separate Device subobjects, each with its own serialNumber.

When you write c.serialNumber, the compiler genuinely cannot tell which of the two you mean, so it refuses rather than guessing.

If you're looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you're a complete beginner or looking to solidify your understanding. Just $19.

The Bad Fix: Explicit Qualification

You can disambiguate by naming the path:

int main() {
    Copier c;

    c.Printer::serialNumber = 42;
    c.Scanner::serialNumber = 99;

    std::cout << c.Printer::serialNumber << "\n";   // 42
    std::cout << c.Scanner::serialNumber << "\n";   // 99
}

It compiles, but look at what it admits: the copier has two different serial numbers. That’s not a naming problem you worked around — it’s a modelling error. A copier is one physical device and should have one serial number. Qualification silences the compiler while leaving the actual bug in place.


The Real Fix: Virtual Inheritance

Tell the middle classes to share the base rather than each owning one. Add virtual to their inheritance:

#include <iostream>

class Device {
public:
    int serialNumber = 0;
    void powerOn() { std::cout << "Powering on\n"; }
};

class Printer : virtual public Device {
public:
    void print() { std::cout << "Printing\n"; }
};

class Scanner : virtual public Device {
public:
    void scan() { std::cout << "Scanning\n"; }
};

class Copier : public Printer, public Scanner {
public:
    void copy() {
        scan();
        print();
    }
};

int main() {
    Copier c;

    c.powerOn();              // works — only one Device now
    c.serialNumber = 42;      // works — only one serialNumber

    std::cout << c.serialNumber << "\n";   // 42
    c.copy();
}
Powering on
42
Scanning
Printing

Now a Copier contains exactly one Device subobject, shared by both the Printer and Scanner parts. The ambiguity is gone because there’s genuinely nothing left to be ambiguous about.

The key detail: virtual goes on the middle classes, not on Copier. Printer and Scanner are the ones that must agree to share. By the time you write Copier, it’s too late.


The Constructor Rule You’ll Hit Next

Virtual inheritance changes who constructs the base. Normally each class constructs its own base — but there’s only one shared Device here, so that can’t work. Instead, the most derived class constructs the virtual base directly:

#include <iostream>

class Device {
public:
    int serialNumber;
    Device(int sn) : serialNumber(sn) {
        std::cout << "Device(" << sn << ")\n";
    }
};

class Printer : virtual public Device {
public:
    Printer(int sn) : Device(sn) {}
};

class Scanner : virtual public Device {
public:
    Scanner(int sn) : Device(sn) {}
};

class Copier : public Printer, public Scanner {
public:
    Copier(int sn) : Device(sn), Printer(sn), Scanner(sn) {}
    //               ^^^^^^^^^^ required, and it is the one that runs
};

int main() {
    Copier c(1234);
    std::cout << c.serialNumber << "\n";
}
Device(1234)
1234

Device(sn) appears three times in the source, but the constructor runs once. The calls in Printer and Scanner are ignored when they’re part of a Copier; only Copier’s own call takes effect. And if you omit Device(sn) from Copier’s initialiser list, the code won’t compile unless Device has a default constructor.

This surprises people, so it’s worth stating plainly: with virtual bases, the most derived class is always responsible for initialising them, no matter how deep the hierarchy.


Should You Use Multiple Inheritance At All?

Virtual inheritance costs you something real — objects get an extra pointer to locate the shared base, member access is slightly slower, and construction rules get subtle. So:

Avoid multiple inheritance for classes carrying data. Prefer composition: give Copier a Printer member and a Scanner member instead of inheriting from both. It’s simpler to reason about and there’s no diamond to solve.

It’s fine for interfaces — abstract classes with only pure virtual functions and no data members. Inheriting from Drawable and Serializable duplicates no state, so the diamond problem never bites. This is the pattern Java and C# formalised as interface, and it’s the one case where multiple inheritance in C++ is uncontroversial.



Take Your C++ Further

If you’re looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you’re a complete beginner or looking to solidify your understanding. Just $19.

👉 Get the C++ Better Explained Ebook — $19

📋

Free Download: The 10 Mistakes Every C++ Beginner Makes

A free 1-page checklist that shows the exact traps that slow down every C++ beginner — so you can avoid them from day one.

🔒 No spam. Unsubscribe anytime.


Share this post on:

Written by

Sahil Bora

Software Engineer. Author and creator of C++ Better Explained.


Previous Post
Binary to Decimal in C++: 3 Ways to Convert (With Full Code)
Next Post
Inline Functions in C++: What the inline Keyword Really Does

Keep Learning