C++ Diamond Problem: Multiple Inheritance & Virtual Bases

Key takeaways

In C++, inheriting the same base along two paths gives you two copies of it. Virtual inheritance merges them into one, but it also changes who constructs the base, which casts are allowed, and what each access costs. This guide covers those rules and when to avoid the diamond altogether.

What is the diamond problem?

In multiple inheritance, a common base can be duplicated.

    A
   / \
  B   C
   \ /
    D
class A { int x; };
class B : public A {};
class C : public A {};
class D : public B, public C {};  // A appears twice

The picture is a little misleading. It suggests that D has one A at the top, reached along two paths. In C++, ordinary inheritance means containment: every B object contains a complete A subobject, and so does every C. A D contains a B and a C, so it contains two separate A objects, each with its own x. The diagram of what is actually in memory is two parallel lines, not a diamond.

Many other languages avoid this by design. Java and C# allow multiple inheritance only of interfaces, which have no data. Python allows multiple inheritance of classes but builds a single linear method resolution order, so a shared base appears once. C++ gives you the choice explicitly: duplicate subobjects by default, or one shared subobject with virtual inheritance.

Two Copies of the Base Class

#include <iostream>

class Animal {
public:
    void eat() {
        std::cout << "Eating" << std::endl;
    }
};
class Mammal : public Animal {};
class Bird : public Animal {};
class Bat : public Mammal, public Bird {
    // Animal inherited twice
};
int main() {
    Bat b;
    // b.eat();  // error: ambiguous
    b.Mammal::eat();  // must qualify
    // Animal* a = &b;  // error: ambiguous conversion
}

The ambiguity error is the compiler telling you that there are two Animal objects and it does not know which one you mean. Qualifying the call (b.Mammal::eat()) compiles, but it is usually a sign of a design problem: if Animal had state, such as hunger, the Mammal part and the Bird part of the same bat would each have their own, and feeding one would not feed the other. The conversion Animal* a = &b is also ambiguous, which makes a Bat impossible to pass directly to any function that takes an Animal*. You would have to cast through one side first.

Virtual inheritance fix

class Animal {
public:
    void eat() {
        std::cout << "Eating" << std::endl;
    }
};
class Mammal : virtual public Animal {};
class Bird : virtual public Animal {};
class Bat : public Mammal, public Bird {};
int main() {
    Bat b;
    b.eat();         // OK: single Animal
    Animal* a = &b;  // OK: unambiguous
}

virtual here means “share this base with any other class in the same object that also inherits it virtually”. A Bat now contains exactly one Animal. Notice where the keyword goes: on Mammal and Bird, the middle of the diamond, not on Bat. That is the uncomfortable part of the design. Whoever writes Mammal has to predict that some future class will combine it with another Animal subclass. If Mammal was written with plain inheritance, the class that creates the diamond cannot fix it without changing its bases.

The standard library itself uses this technique: std::basic_iostream inherits from both basic_istream and basic_ostream, and both inherit virtually from basic_ios, so an std::iostream has a single stream state and buffer. It is a good example of when a diamond is a deliberate design, not an accident.

Constructing the Virtual Base, Interfaces, and Composition

Who constructs the virtual base

#include <iostream>

class Base {
protected:
    int value;
public:
    Base(int v) : value(v) {}
    int getValue() const { return value; }
};
class Left : virtual public Base {
public:
    Left(int v) : Base(v) {}
};
class Right : virtual public Base {
public:
    Right(int v) : Base(v) {}
};
class Bottom : public Left, public Right {
public:
    Bottom(int v) : Base(v), Left(v + 1), Right(v + 2) {}
};
int main() {
    Bottom b(10);
    std::cout << b.getValue() << std::endl;  // 10, not 11 or 12
}

This is the rule that surprises people most. With a virtual base, only the most-derived class (the type actually being created, here Bottom) initializes it. The Base(v) in Left’s and Right’s initializer lists is silently ignored when they are constructed as part of a Bottom. It only runs when you create a standalone Left or Right. The example passes different values on purpose to make that visible: the result is 10.

The consequences are bigger than they look. If Bottom does not mention Base at all, the compiler tries Base’s default constructor, and since Base has none, compilation fails with an error pointing at Bottom, even though Left and Right “clearly” initialize it. And every class that ever derives from Bottom must initialize Base itself, because it becomes the most-derived class. Adding a new level to the hierarchy means updating the constructor at the new bottom.

This is the diamond bug I find hardest to spot in review, because each constructor looks correct on its own. The typical case is an intermediate class that computes an argument for the virtual base, such as a default buffer size or a name, and a reviewer assumes that logic runs. It does not, when that class is not the most-derived one. The value comes from the most-derived class instead, and the intermediate class’s logic is quietly bypassed. When I see a virtual base with a non-default constructor, I check the initializer list of every class that can be instantiated.

Interface-style multiple inheritance

#include <iostream>

class IDrawable {
public:
    virtual void draw() = 0;
    virtual ~IDrawable() = default;
};
class ISerializable {
public:
    virtual void serialize() = 0;
    virtual ~ISerializable() = default;
};
class Shape : public IDrawable, public ISerializable {
public:
    void draw() override {
        std::cout << "Drawing" << std::endl;
    }
    
    void serialize() override {
        std::cout << "Serializing" << std::endl;
    }
};

This is the form of multiple inheritance that almost every C++ style guide accepts, and it is the same idea as Java and C# interfaces. Pure abstract classes without data members cannot produce the “two copies of the state” problem, so there is no diamond to worry about even if two interfaces share a common base interface. Each base does still add a vtable pointer to Shape, and converting a Shape* to an ISerializable* adjusts the pointer value by an offset. That is invisible in normal code, but it matters if you ever compare pointers to different bases or cast through void*.

Constructor order

#include <iostream>

class A {
public:
    A() { std::cout << "A" << std::endl; }
};
class B : virtual public A {
public:
    B() { std::cout << "B" << std::endl; }
};
class C : virtual public A {
public:
    C() { std::cout << "C" << std::endl; }
};
class D : public B, public C {
public:
    D() { std::cout << "D" << std::endl; }
};
int main() {
    D d;
    // Output: A B C D
}

Virtual bases are always constructed first, before any non-virtual base, regardless of where they appear in the hierarchy. Then the direct bases are constructed in the order they are declared in the class definition (B then C), not the order they appear in the initializer list. Destruction runs in exactly the reverse order. A is printed once, which confirms there is a single A subobject.

Alternative—composition

// Inheritance: a Car "is an" Engine and "is a" Wheels (it isn't)
class Engine {};
class Wheels {};
class CarByInheritance : public Engine, public Wheels {};

// Composition: a Car "has an" Engine and "has" Wheels
class Car {
private:
    Engine engine;
    Wheels wheels;
};

The inheritance version compiles, but it makes claims that are false. A CarByInheritance* converts implicitly to Engine*, so the car can be passed to any function that expects an engine, and all of Engine’s public methods become part of the car’s interface. Composition keeps the parts private and lets Car expose only the operations that make sense for a car, such as start(), which then calls engine.start(). The test is the usual one: if “D is a B” is not true in the problem domain, do not inherit.

Ambiguous Calls, Final Overriders, and Cast Costs

Ambiguous calls

// Without virtual inheritance
class Base {
public:
    void func() {}
};
class D1 : public Base {};
class D2 : public Base {};
class Final : public D1, public D2 {};
// Final f; f.func();  // error: ambiguous

// With virtual inheritance
class VD1 : virtual public Base {};
class VD2 : virtual public Base {};
class VFinal : public VD1, public VD2 {};
// VFinal vf; vf.func();  // OK

No unique final overrider

class Base {
public:
    virtual void describe() = 0;
    virtual ~Base() = default;
};
class D1 : virtual public Base {
public:
    void describe() override {}
};
class D2 : virtual public Base {
public:
    void describe() override {}
};
// error: no unique final overrider for 'describe'
// class Final : public D1, public D2 {};
class Final : public D1, public D2 {
public:
    void describe() override { D1::describe(); D2::describe(); }
};

With a single shared Base, the object has one vtable slot for describe. If both sides of the diamond override it, the compiler cannot choose, so Final must provide its own override. This is correct behavior, not a compiler limitation: only Final knows how the two behaviors should be combined.

Casts and layout

class Base { public: virtual ~Base() = default; };
class D : virtual public Base {};

Base* b = new D;
// D* d1 = static_cast<D*>(b);   // error: cannot static_cast from a virtual base
D* d2 = dynamic_cast<D*>(b);     // OK: uses runtime type information
delete b;

With a virtual base, the position of the Base subobject inside a D is not fixed. It depends on the most-derived type of the object. So the compiler cannot compute the downcast at compile time, static_cast is rejected, and you need dynamic_cast. The same flexible layout is why accessing a member of a virtual base often costs an extra indirection: the offset is looked up at runtime, usually through the vtable. sizeof also grows, because each class that inherits virtually needs a way to find the shared base. For most code, this cost is negligible, but it is real in tight loops over many small objects.

Complexity

// Hard-to-follow multiple inheritance
class A {};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
class E : public D {};  // E now must initialize A itself
// Often clearer: interface split
class IInterface1 {};
class IInterface2 {};
class Implementation : public IInterface1, public IInterface2 {};

Alternatives

// 1. Composition
class Car {
    Engine engine;
    Wheels wheels;
};
// 2. Interface-only inheritance
class IDrawable { public: virtual void draw() = 0; virtual ~IDrawable() = default; };
class IClickable { public: virtual void click() = 0; virtual ~IClickable() = default; };
// 3. Mixins via templates (CRTP)
template <typename Derived>
class Printable {
public:
    void print() const { static_cast<const Derived&>(*this).printImpl(); }
};

Which alternative fits depends on what the shared base was for. If it holds shared state that both sides need, composition is usually right: store the state once in the combined class and give each part a reference or pass it in. If it only defines a shared interface, split it into pure interfaces, and the diamond problem disappears. If it provides reusable behavior, a CRTP mixin (see CRTP) adds it at compile time without any shared runtime base. And if the class hierarchy only exists to handle a closed set of alternatives (a Bat that is either a Mammal or a Bird depending on context), std::variant may express it better than inheritance at all.

FAQ

Q1: When does the diamond problem appear?

A: When a class inherits the same non-virtual base along more than one path.

Q2: How do I fix it?

A: Virtual inheritance if you really need one shared base object; otherwise composition or pure interfaces.

Q3: What does virtual inheritance cost?

A: Slightly larger objects, an extra indirection to reach the base, dynamic_cast instead of static_cast for downcasts, and the rule that the most-derived class initializes the base.

A: Multiple inheritance of thin interfaces is fine and common. Multiple inheritance of classes with state is usually a design smell.

Q5: What is the construction order?

A: Virtual bases first (initialized by the most-derived class), then direct bases in declaration order, then members, then the class’s own constructor body.