Skip to Content
Course content

30: Virtual Inheritance and the Diamond Problem

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Multiple inheritance is one of those features in C++ that feels like a superpower until you accidentally create a "diamond." I've seen this trip up even senior devs when they're rushing a hierarchy. It happens when two classes inherit from the same base, and then a fourth class inherits from both of those. Suddenly, the compiler is confused because it sees two different paths to the same original grandparent.

Building a Device Hierarchy

Let's build a simple system for a smart-home app. We'll start with a base class called Device. Every device has a deviceId. Then, we'll have a Camera and a Phone, both of which are devices. Finally, we'll create a Smartphone, which is logically both a phone and a camera.

class Device {
public:
    int deviceId = 101;
    void powerOn() { /* ... */ }
};

class Camera : public Device {
public:
    void takePhoto() { /* ... */ }
};

class Phone : public Device {
public:
    void makeCall() { /* ... */ }
};

class Smartphone : public Camera, public Phone {
public:
    void browseWeb() { /* ... */ }
};

Hitting the Ambiguity Wall

Everything looks fine on paper, right? But here is where I usually make my first mistake. I'll try to use the Smartphone and access that deviceId from the base Device class. Let's see what happens when we try to compile this:

Smartphone myPhone;
// This line will cause a compiler error!
int id = myPhone.deviceId; 

The compiler is going to throw a fit. It'll tell you that Device::deviceId is ambiguous. Why? Because Smartphone inherited from Camera (which has a Device) and from Phone (which also has a Device). In the eyes of the compiler, myPhone actually contains two separate copies of the Device class. It has no idea which one you're talking about.

Cleaning Up with Virtual Inheritance

To fix this, we need to tell C++ that we only ever want one shared instance of the base class, regardless of how many paths lead back to it. We do this using the virtual keyword during inheritance. I'll go back and modify the Camera and Phone classes.

class Camera : virtual public Device {
public:
    void takePhoto() { /* ... */ }
};

class Phone : virtual public Device {
public:
    void makeCall() { /* ... */ }
};

// Now Smartphone only inherits one shared 'Device' object
class Smartphone : public Camera, public Phone {
public:
    void browseWeb() { /* ... */ }
};

By adding virtual, we've told the compiler: "If anyone else inherits from Device and eventually merges into a child class, please share the same base object." Now, myPhone.deviceId works perfectly because there's only one deviceId in the whole object.

The Cost of the Fix

I should mention that virtual inheritance isn't free. It adds a bit of overhead because the compiler has to use a pointer (a "virtual base pointer") to find where the shared base class actually lives in memory. In 99% of your projects, this performance hit is negligible. But if you're writing a high-frequency trading engine or a physics kernel, you'll want to be mindful of it. For most of us, it's a small price to pay to avoid the diamond problem's headaches.




📋 Practical Task

Fixing the Multi-Function Printer Hierarchy

You've been handed a piece of legacy code for a printer driver. The developer who wrote it forgot about the diamond problem, and the code currently refuses to compile. Your task is to fix the inheritance structure so that the MultiFunctionPrinter can access the serialNumber from the HardwareDevice base class without ambiguity.

#include <iostream>
#include <string>

class HardwareDevice {
public:
    std::string serialNumber = "SN-999-XYZ";
};

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

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

class MultiFunctionPrinter : public Scanner, public Printer {
public:
    void allInOne() {
        scan();
        print();
    }
};

int main() {
    MultiFunctionPrinter mfp;
    // ERROR: request for member 'serialNumber' is ambiguous
    std::cout < "Device Serial: " < mfp.serialNumber < std::endl;
    return 0;
}

Requirements:

  • Modify the inheritance of Scanner and Printer to resolve the ambiguity.
  • Ensure the main function can print the serialNumber successfully.
  • Do not change the structure of the classes (i.e., do not remove the multiple inheritance).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.