Virtual Inheritance in C++
Table of Contents
- The Diamond Problem
- Virtual Inheritance Solution
- Memory Layout Visualization
- Constructor Order
- Virtual Base Pointer (vbptr)
- When to Use
- Best Practices
The Diamond Problem
When a class inherits from two classes that share a common base, we get the Diamond Problem (also called the Deadly Diamond of Death).
Visual Representation
┌─────────────┐
│ Animal │
│ + age: int │
└─────────────┘
△
┌────────┴────────┐
│ │
┌─────────┐ ┌─────────┐
│ Mammal │ │ Bird │
│ : Animal│ │ : Animal│
└─────────┘ └─────────┘
△ △
└────────┬────────┘
│
┌─────────┐
│ Bat │
│: Mammal │
│: Bird │
└─────────┘
The Problem Explained
When Bat inherits from both Mammal and Bird, and both inherit from Animal, the Bat class ends up with two separate copies of the Animal base class:
Without Virtual Inheritance:
Bat Object Memory Layout
┌──────────────────────────┐
│ Mammal's Animal Copy │◄── First Animal instance
│ ├── age = 5 │
│ └── (vptr if virtual) │
├──────────────────────────┤
│ Mammal's own data │
├──────────────────────────┤
│ Bird's Animal Copy │◄── Second Animal instance (DUPLICATE!)
│ ├── age = 3 │ Different value possible!
│ └── (vptr if virtual) │
├──────────────────────────┤
│ Bird's own data │
├──────────────────────────┤
│ Bat's own data │
└──────────────────────────┘
Without Virtual Inheritance
class Animal { public: int age; };
class Mammal : public Animal { };
class Bird : public Animal { };
class Bat : public Mammal, public Bird { };
Bat b;
// b.age = 5; // ERROR: ambiguous - which age?
b.Mammal::age = 5; // Sets Mammal's Animal copy
b.Bird::age = 3; // Sets Bird's Animal copy (DIFFERENT!)
// Size comparison shows the problem
cout << sizeof(Animal) << endl; // 4 bytes (just age)
cout << sizeof(Bat) << endl; // 8+ bytes (TWO copies of age!)
Problems with this approach:
- ❌ Ambiguity when accessing base class members
- ❌ Wasted memory (duplicate data)
- ❌ Inconsistent state (two different values for same logical property)
- ❌ Difficult to maintain (changes must be synchronized)
Virtual Inheritance Solution
Use the virtual keyword when inheriting from the shared base class:
Syntax
class Animal { public: int age; };
class Mammal : virtual public Animal { }; // ◄── virtual keyword
class Bird : virtual public Animal { }; // ◄── virtual keyword
class Bat : public Mammal, public Bird { };
Bat b;
b.age = 5; // ✅ OK! Only ONE copy of Animal
Memory Layout with Virtual Inheritance
With Virtual Inheritance:
Bat Object Memory Layout
┌──────────────────────────┐
│ Mammal subobject │
│ ├── vbptr ──────────────┼──┐ (virtual base pointer)
│ └── Mammal's own data │ │
├──────────────────────────┤ │
│ Bird subobject │ │
│ ├── vbptr ──────────────┼──┤ (virtual base pointer)
│ └── Bird's own data │ │
├──────────────────────────┤ │
│ Bat's own data │ │
├──────────────────────────┤ │
│ ▼▼▼ SHARED ▼▼▼ │ │
│ Animal (SINGLE COPY) │◄─┘ Both vbptrs point here!
│ └── age = 5 │
└──────────────────────────┘
Complete Example
#include <iostream>
using namespace std;
class Animal {
public:
int age;
string name;
Animal(const string& n = "Unknown") : age(0), name(n) {
cout << "Animal(" << name << ") constructed" << endl;
}
virtual ~Animal() {
cout << "Animal(" << name << ") destroyed" << endl;
}
void describe() const {
cout << name << " is " << age << " years old" << endl;
}
};
class Mammal : virtual public Animal {
public:
bool hasFur;
Mammal() : hasFur(true) {
cout << "Mammal constructed" << endl;
}
void nurse() { cout << name << " is nursing" << endl; }
};
class Bird : virtual public Animal {
public:
bool canFly;
Bird() : canFly(true) {
cout << "Bird constructed" << endl;
}
void layEggs() { cout << name << " laid eggs" << endl; }
};
class Bat : public Mammal, public Bird {
public:
Bat(const string& n) : Animal(n) { // Must initialize Animal directly!
cout << "Bat constructed" << endl;
canFly = true; // Bats can fly
hasFur = true; // Bats have fur
}
void echolocate() { cout << name << " is using echolocation" << endl; }
};
int main() {
cout << "=== Creating Bat ===" << endl;
Bat bat("Bruce");
bat.age = 3; // ✅ Unambiguous - single Animal
bat.describe(); // ✅ Inherited from Animal
bat.nurse(); // ✅ Inherited from Mammal
bat.layEggs(); // ✅ Inherited from Bird
bat.echolocate(); // ✅ Bat's own method
cout << "\n=== Size Comparison ===" << endl;
cout << "sizeof(Animal): " << sizeof(Animal) << " bytes" << endl;
cout << "sizeof(Mammal): " << sizeof(Mammal) << " bytes" << endl;
cout << "sizeof(Bird): " << sizeof(Bird) << " bytes" << endl;
cout << "sizeof(Bat): " << sizeof(Bat) << " bytes" << endl;
return 0;
}
How Virtual Inheritance Works
Without virtual:
┌──────────────────────────────────────────────────┐
│ Mammal has Animal │ Bird has Animal │ = 2 Animals│
└──────────────────────────────────────────────────┘
With virtual:
┌──────────────────────────────────────────────────┐
│ Mammal ──┐ │
│ ├──► Shared Animal ◄──┤ = 1 Animal │
│ Bird ────┘ │
└──────────────────────────────────────────────────┘
Key Difference:
• Virtual inheritance adds a level of indirection (vbptr)
• The shared base is placed at a fixed offset from the complete object
• All paths to the virtual base lead to the same instance
Memory Layout Visualization
Detailed Memory Comparison
┌─────────────────────────────────────────────────────────────────┐
│ WITHOUT Virtual Inheritance │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Offset Data Belongs To │
│ ────── ──── ────────── │
│ 0x00 ┌─────────────┐ │
│ │ Animal::age │ Mammal's Animal copy │
│ └─────────────┘ │
│ 0x04 ┌─────────────┐ │
│ │ Mammal data │ Mammal │
│ └─────────────┘ │
│ 0x08 ┌─────────────┐ │
│ │ Animal::age │ Bird's Animal copy (DUPLICATE)│
│ └─────────────┘ │
│ 0x0C ┌─────────────┐ │
│ │ Bird data │ Bird │
│ └─────────────┘ │
│ 0x10 ┌─────────────┐ │
│ │ Bat data │ Bat │
│ └─────────────┘ │
│ │
│ Problem: Two separate 'age' fields exist! │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ WITH Virtual Inheritance │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Offset Data Belongs To │
│ ────── ──── ────────── │
│ 0x00 ┌─────────────┐ │
│ │ vbptr ─────┼───────────────────┐ (points to │
│ └─────────────┘ │ shared Animal) │
│ 0x08 ┌─────────────┐ │ │
│ │ Mammal data │ Mammal │ │
│ └─────────────┘ │ │
│ 0x10 ┌─────────────┐ │ │
│ │ vbptr ─────┼───────────────────┤ │
│ └─────────────┘ │ │
│ 0x18 ┌─────────────┐ │ │
│ │ Bird data │ Bird │ │
│ └─────────────┘ │ │
│ 0x20 ┌─────────────┐ │ │
│ │ Bat data │ Bat │ │
│ └─────────────┘ │ │
│ 0x28 ┌═════════════┐ │ │
│ ║ Animal::age ║◄──────────────────┘ │
│ ╚═════════════╝ SHARED Animal (SINGLE COPY) │
│ │
│ Solution: Only ONE 'age' field, accessed via vbptr │
│ │
└─────────────────────────────────────────────────────────────────┘
Constructor Order
Execution Flow Diagram
Creating a Bat object: Bat bat("Bruce");
┌─────────────────────────────────────────────────────┐
│ 1. VIRTUAL BASES FIRST (in declaration order) │
│ ┌─────────────────────────────────────────────┐ │
│ │ Animal("Bruce") ───────────────────────────►│ │
│ │ "Animal(Bruce) constructed" │ │
│ └─────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────┤
│ 2. DIRECT BASES (in declaration order) │
│ ┌─────────────────────────────────────────────┐ │
│ │ Mammal() ──────────────────────────────────►│ │
│ │ "Mammal constructed" │ │
│ └─────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Bird() ────────────────────────────────────►│ │
│ │ "Bird constructed" │ │
│ └─────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────┤
│ 3. MOST-DERIVED CLASS │
│ ┌─────────────────────────────────────────────┐ │
│ │ Bat() ─────────────────────────────────────►│ │
│ │ "Bat constructed" │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
Destruction Order (REVERSE):
Bat destroyed → Bird destroyed → Mammal destroyed → Animal destroyed
Virtual Base Initialized by Most-Derived Class
This is the most important rule! The most-derived class (the one being instantiated) is responsible for calling the virtual base constructor:
class Base {
public:
Base(int x) { cout << "Base(" << x << ")" << endl; }
};
class Left : virtual public Base {
public:
Left() : Base(1) { // ◄── This initialization is IGNORED
cout << "Left" << endl; // when creating Derived!
}
};
class Right : virtual public Base {
public:
Right() : Base(2) { // ◄── This initialization is also IGNORED
cout << "Right" << endl; // when creating Derived!
}
};
class Derived : public Left, public Right {
public:
// MUST explicitly call Base() - it's the most-derived class
Derived() : Base(999), Left(), Right() { // ◄── Base(999) is used!
cout << "Derived" << endl;
}
};
int main() {
cout << "Creating Left:" << endl;
Left left; // Output: Base(1) Left
cout << "\nCreating Right:" << endl;
Right right; // Output: Base(2) Right
cout << "\nCreating Derived:" << endl;
Derived d; // Output: Base(999) Left Right Derived
return 0;
}
Visual: Constructor Responsibility Chain
When creating a Derived object:
┌────────────────────────────────────────────────────────┐
│ Derived (Most-Derived) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Derived() : Base(999), Left(), Right() { } │ │
│ │ ▲ │ │
│ │ │ Derived MUST initialize Base │ │
│ └────────────┼──────────────────────────────────────┘ │
│ │ │
│ ┌──────────┴──────────────────────────────────┐ │
│ │ Left : virtual public Base │ │
│ │ Left() : Base(1) { } ◄── IGNORED │ │
│ └──────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Right : virtual public Base │ │
│ │ Right() : Base(2) { } ◄── IGNORED │ │
│ └──────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Base (Virtual Base) │ │
│ │ Initialized with 999 (from Derived) │ │
│ └──────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
Key Rule: Virtual base initialization in intermediate classes
(Left, Right) is ONLY used when that class is the
most-derived class being instantiated.
Virtual Base Pointer (vbptr)
What is vbptr?
The virtual base pointer (vbptr) is a hidden pointer added by the compiler to classes using virtual inheritance. It points to the shared virtual base class instance.
┌──────────────────────────────────────────────────────────────────┐
│ How vbptr enables virtual inheritance: │
├──────────────────────────────────────────────────────────────────┤
│ │
│ Mammal Object Bird Object │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ vbptr ──────┼──┐ │ vbptr ──────┼──┐ │
│ │ Mammal data │ │ │ Bird data │ │ │
│ └─────────────┘ │ └─────────────┘ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Virtual Base Table │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ Offset to Animal from Mammal: +XX bytes │ │ │
│ │ │ Offset to Animal from Bird: +YY bytes │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Animal (Shared Instance) │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ age = 5 │ │ │
│ │ │ name = "Bruce" │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
Performance Implications
| Aspect | Without Virtual Inheritance | With Virtual Inheritance |
|---|---|---|
| Memory | Duplicate base data | Single copy + vbptrs |
| Access Speed | Direct access | Indirect (via vbptr) |
| Object Size | Larger (duplicated) | Smaller + pointer overhead |
| Complexity | Simple | Slightly more complex |
// Access without virtual inheritance:
// mov eax, [object + offset_to_age] // Direct memory access
// Access with virtual inheritance:
// mov ebx, [object + vbptr_offset] // Get vbptr
// mov ecx, [ebx + vbtable_offset] // Get offset from vbtable
// mov eax, [object + ecx] // Access member via offset
When to Use
Decision Flowchart
┌─────────────────────────┐
│ Do you have a diamond │
│ inheritance pattern? │
└───────────┬─────────────┘
│
┌──────────────┴──────────────┐
│ │
YES NO
│ │
▼ ▼
┌────────────────────────┐ ┌────────────────────────┐
│ Is the shared base │ │ Virtual inheritance │
│ class an interface │ │ NOT needed. Use │
│ (pure abstract)? │ │ regular inheritance. │
└───────────┬────────────┘ └────────────────────────┘
│
┌──────────┴──────────┐
│ │
YES NO
│ │
▼ ▼
┌─────────────────────┐ ┌──────────────────────────┐
│ USE virtual │ │ Consider alternatives: │
│ inheritance │ │ 1. Composition │
│ (low overhead) │ │ 2. Redesign hierarchy │
└─────────────────────┘ │ 3. Use virtual if needed │
└──────────────────────────┘
✅ Use Virtual Inheritance For
1. Interface Hierarchies
// Multiple interfaces that share a common base interface
class IBase {
public:
virtual ~IBase() = default;
virtual void common() = 0;
};
class IReader : virtual public IBase {
public:
virtual void read() = 0;
};
class IWriter : virtual public IBase {
public:
virtual void write() = 0;
};
class File : public IReader, public IWriter {
public:
void common() override { /* ... */ }
void read() override { /* ... */ }
void write() override { /* ... */ }
};
2. Mixin Patterns
class Entity { // Common base
public:
int id;
string name;
};
class Renderable : virtual public Entity {
public:
void render() { cout << "Rendering " << name << endl; }
};
class Collidable : virtual public Entity {
public:
void checkCollision() { cout << "Checking " << name << endl; }
};
class GameObject : public Renderable, public Collidable {
public:
GameObject(const string& n) {
name = n; // ✅ Single 'name' member
}
};
3. Component-Based Design
class Component {
protected:
class Entity* owner;
public:
virtual void update() = 0;
};
class PhysicsComponent : virtual public Component {
public:
void update() override { /* physics */ }
};
class RenderComponent : virtual public Component {
public:
void update() override { /* render */ }
};
❌ Avoid Virtual Inheritance When
| Scenario | Reason | Alternative |
|---|---|---|
| No diamond pattern | Unnecessary overhead | Use regular inheritance |
| Performance-critical code | Indirect access via vbptr | Composition or templates |
| Simple hierarchies | Adds complexity | Keep it simple |
| Base has no state | No duplication anyway | Can use without virtual |
Best Practices
✅ Do
// 1. Always initialize virtual base in most-derived class
class D : public B, public C {
public:
D() : VirtualBase(args), B(), C() { } // VirtualBase FIRST
};
// 2. Use virtual inheritance with abstract/interface classes
class ISerializable {
public:
virtual ~ISerializable() = default;
virtual void serialize() = 0;
};
class IPrintable : virtual public IBase { };
class IStorable : virtual public IBase { };
// 3. Keep virtual base classes simple
class SimpleBase {
public:
int id; // Minimal state
virtual ~SimpleBase() = default;
};
// 4. Document virtual inheritance in your codebase
/// @note Uses virtual inheritance from Entity base class
class Player : virtual public Entity { };
// 5. Consider using virtual destructors
class VirtualBase {
public:
virtual ~VirtualBase() = default; // ◄── Important!
};
❌ Don't
// 1. DON'T mix virtual and non-virtual inheritance of same base
class B : public A { };
class C : virtual public A { };
class D : public B, public C { }; // ❌ Confusing! A appears twice
// 2. DON'T create complex virtual base constructors
class ComplexBase {
public:
// Hard to manage across inheritance hierarchy
ComplexBase(int a, string b, vector<int> c, map<string,int> d) { }
};
// 3. DON'T forget to call virtual base constructor
class Derived : public Left, public Right {
public:
Derived() : Left(), Right() { } // ❌ Missing Base() - uses default
// Should be:
// Derived() : Base(args), Left(), Right() { }
};
// 4. DON'T overuse virtual inheritance
// If you don't have a diamond problem, don't use it
class Simple : virtual public Base { }; // ❌ Unnecessary overhead
// 5. DON'T ignore the performance implications
// Virtual inheritance adds indirection - consider for hot paths
Common Mistakes Visualized
MISTAKE 1: Mixed Virtual/Non-Virtual Inheritance
═══════════════════════════════════════════════════
class A { int x; };
class B : public A { }; // Non-virtual
class C : virtual public A { }; // Virtual
class D : public B, public C { }; // ❌ PROBLEM!
D object memory layout:
┌────────────────────────┐
│ B subobject │
│ └── A (non-virtual) │ ◄── First copy of A
├────────────────────────┤
│ C subobject │
│ └── vbptr ───────────┐│
├────────────────────────┤│
│ A (virtual) ◄────────┘│ ◄── Second copy of A
└────────────────────────┘
Result: D has TWO copies of A! The diamond wasn't solved.
MISTAKE 2: Forgetting Virtual Base Constructor
══════════════════════════════════════════════════
class Base {
int value;
public:
Base(int v) : value(v) { } // No default constructor!
};
class Left : virtual public Base {
public:
Left() : Base(1) { }
};
class Derived : public Left, public Right {
public:
Derived() : Left(), Right() { } // ❌ ERROR!
// Compiler error: no default constructor for Base
// Fix:
Derived() : Base(999), Left(), Right() { } // ✅
};
Quick Reference
┌─────────────────────────────────────────────────────────────────┐
│ VIRTUAL INHERITANCE CHEATSHEET │
├─────────────────────────────────────────────────────────────────┤
│ │
│ SYNTAX │
│ ─────── │
│ class Derived : virtual public Base { }; │
│ ▲ │
│ └── Add 'virtual' keyword here │
│ │
│ DIAMOND SOLUTION │
│ ───────────────── │
│ class A { }; │
│ class B : virtual public A { }; ◄── Both use virtual │
│ class C : virtual public A { }; ◄── Both use virtual │
│ class D : public B, public C { }; │
│ // D has single shared A │
│ │
│ CONSTRUCTOR RULES │
│ ───────────────── │
│ 1. Virtual bases constructed FIRST │
│ 2. Most-derived class initializes virtual bases │
│ 3. Intermediate class initializations are IGNORED │
│ │
│ MEMORY LAYOUT │
│ ───────────── │
│ • vbptr (virtual base pointer) added to each subobject │
│ • Virtual base placed at fixed offset in complete object │
│ • Single shared instance of virtual base │
│ │
│ WHEN TO USE │
│ ─────────── │
│ ✅ Diamond inheritance patterns │
│ ✅ Interface hierarchies │
│ ✅ Mixin patterns with shared base │
│ ❌ Simple single inheritance │
│ ❌ Performance-critical hot paths │
│ │
└─────────────────────────────────────────────────────────────────┘
Compile & Run
g++ -std=c++17 -Wall -Wextra examples.cpp -o examples
./examples