Menu
Coddy logo textTech

Virtual Inheritance

Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 54 of 104.

The diamond problem occurs when a class inherits from two classes that share a common base class. Without special handling, the derived class ends up with two copies of the common base, causing ambiguity and wasted memory.

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'?

The Bat class contains two separate Animal subobjects: one through Mammal and one through Bird. This creates the diamond-shaped inheritance diagram that gives the problem its name.

Virtual inheritance solves this by ensuring only one copy of the common base exists. Add the virtual keyword when inheriting from the shared base:

class Animal {
public:
    int age;
    Animal(int a = 0) : age(a) {}
};

class Mammal : virtual public Animal {
public:
    Mammal(int a = 0) : Animal(a) {}
};

class Bird : virtual public Animal {
public:
    Bird(int a = 0) : Animal(a) {}
};

class Bat : public Mammal, public Bird {
public:
    Bat(int a) : Animal(a), Mammal(a), Bird(a) {}
};

Bat b(5);
b.age = 10;  // Works! Only one 'age' exists

Notice that Bat must directly initialize Animal in its constructor. With virtual inheritance, the most derived class is responsible for constructing the virtual base, regardless of the intermediate classes.

challenge icon

Challenge

Easy

Let's build a worker management system that demonstrates how virtual inheritance solves the diamond problem. You'll create a hierarchy where a TeamLead inherits from both Developer and Manager, which both share a common Employee base class.

You'll organize your code across four files:

  • Employee.h: Define the common base class Employee with:
    • A protected std::string name and int id
    • A constructor that takes both values and prints: Employee [<name>] hired with ID <id>
    • A public getInfo() method that prints: Employee: <name> (ID: <id>)
    • A virtual destructor that prints: Employee [<name>] record closed
  • Developer.h: Define a Developer class that uses virtual public inheritance from Employee:
    • A protected std::string language member
    • A constructor that takes name, id, and language — passes name and id to Employee, stores the language, and prints: Developer [<name>] specializes in <language>
    • A public code() method that prints: <name> is coding in <language>
    • A destructor that prints: Developer [<name>] signed off
  • Manager.h: Define a Manager class that uses virtual public inheritance from Employee:
    • A protected int teamSize member
    • A constructor that takes name, id, and team size — passes name and id to Employee, stores the team size, and prints: Manager [<name>] leads a team of <teamSize>
    • A public manage() method that prints: <name> is managing <teamSize> people
    • A destructor that prints: Manager [<name>] stepped down
  • main.cpp: Read four inputs (each on a separate line):
    1. Name (string)
    2. Employee ID (integer)
    3. Programming language (string)
    4. Team size (integer)

    Define a TeamLead class that publicly inherits from both Developer and Manager:

    • A constructor that takes all four parameters and must directly initialize Employee (the virtual base), then Developer and Manager
    • The constructor should print: TeamLead [<name>] ready to lead and code!
    • A showRole() method that calls getInfo(), code(), and manage() in that order
    • A destructor that prints: TeamLead [<name>] promoted out

    Create a TeamLead object inside a block scope, call showRole(), then let it go out of scope. After the block, print: Organization restructured!

For example, with inputs Alice, 101, C++, and 5:

Employee [Alice] hired with ID 101
Developer [Alice] specializes in C++
Manager [Alice] leads a team of 5
TeamLead [Alice] ready to lead and code!
Employee: Alice (ID: 101)
Alice is coding in C++
Alice is managing 5 people
TeamLead [Alice] promoted out
Manager [Alice] stepped down
Developer [Alice] signed off
Employee [Alice] record closed
Organization restructured!

Notice how there's only one Employee constructor call and one Employee destructor call — virtual inheritance ensures only one copy of the shared base exists. The TeamLead must directly initialize Employee because with virtual inheritance, the most derived class is responsible for constructing the virtual base.

Cheat sheet

The diamond problem occurs when a class inherits from two classes that share a common base class, resulting in two copies of the base class and causing ambiguity.

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'?

Virtual inheritance solves the diamond problem by ensuring only one copy of the common base class exists. Use the virtual keyword when inheriting:

class Animal {
public:
    int age;
    Animal(int a = 0) : age(a) {}
};

class Mammal : virtual public Animal {
public:
    Mammal(int a = 0) : Animal(a) {}
};

class Bird : virtual public Animal {
public:
    Bird(int a = 0) : Animal(a) {}
};

class Bat : public Mammal, public Bird {
public:
    Bat(int a) : Animal(a), Mammal(a), Bird(a) {}
};

Bat b(5);
b.age = 10;  // Works! Only one 'age' exists

With virtual inheritance, the most derived class must directly initialize the virtual base class in its constructor, regardless of intermediate classes.

Try it yourself

#include <iostream>
#include <string>
#include "Developer.h"
#include "Manager.h"

using namespace std;

// TODO: Define TeamLead class that publicly inherits from both Developer and Manager
// Remember: With virtual inheritance, TeamLead must directly initialize Employee (the virtual base)
class TeamLead : public Developer, public Manager {
public:
    // TODO: Implement constructor that takes name, id, language, and teamSize
    // Must initialize: Employee first (virtual base), then Developer, then Manager
    // Should print: TeamLead [<name>] ready to lead and code!
    TeamLead(const std::string& name, int id, const std::string& language, int teamSize)
        : Employee(name, id),
          Developer(name, id, language),
          Manager(name, id, teamSize) {
        // TODO: Print constructor message
    }

    // TODO: Implement showRole() method
    // Should call getInfo(), code(), and manage() in that order
    void showRole() {
        // TODO: Call the three methods
    }

    // TODO: Implement destructor
    // Should print: TeamLead [<name>] promoted out
    ~TeamLead() {
        // TODO: Print destructor message
    }
};

int main() {
    // Read inputs
    string name;
    int id;
    string language;
    int teamSize;

    getline(cin, name);
    cin >> id;
    cin.ignore();
    getline(cin, language);
    cin >> teamSize;

    // TODO: Create a TeamLead object inside a block scope
    // Call showRole(), then let it go out of scope
    {
        // TODO: Create TeamLead and call showRole()
    }

    // Print final message after the block
    cout << "Organization restructured!" << endl;

    return 0;
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming