145. Diamond Inheritance Duplication Fix

In embedded firmware, drivers are often built by layering multiple capability classes.
When these layers inherit from a shared hardware base, a diamond inheritance structure can occur.

If this structure is implemented incorrectly, it can lead to:

  • Multiple copies of hardware state
  • Repeated hardware initialization
  • Unsafe or undefined behavior in firmware systems

Your task is to observe this problem and fix it correctly using Embedded C++ best practices.

 

Scenario

You are given the following inheritance structure:

        DeviceCore
        /        \
   CommLayer   PowerLayer
        \        /
        SensorDriver
  • DeviceCore represents shared hardware state
  • CommLayer and PowerLayer both inherit from DeviceCore
  • SensorDriver inherits from both layers

When inheritance is non-virtual, SensorDriver contains two separate copies of DeviceCore.

 

Objective

Modify the program so that:

  • Only one instance of DeviceCore exists inside SensorDriver
  • DeviceCore is initialized exactly once
  • Output clearly proves duplication has been removed
  • The diamond inheritance structure remains intact
  • Initialization of the shared base follows C++ virtual inheritance rules

 

Rules (Strict)

You must follow all rules below:

  • Do NOT remove the diamond structure
  • Do NOT remove multiple inheritance
  • Do NOT use:
    • Dynamic memory allocation
    • Pointers
  • You MAY use:
    • Virtual inheritance
  • You MAY modify inheritance declarations
  • Use only standard input and output
  • Output text and order must match exactly

 

Input

One signed integer value:

id 

Program Flow (Mandatory Order)

  1. Read integer id
  2. Create a SensorDriver object using id
  3. Print the device ID from the driver

Expected Output (After Fix)

Device core initialized
Device ID <id>

⚠️ Note

Before fixing the design,
Device core initialized would be printed twice.

 

Example

Input:

42

Output:

Device core initialized
Device ID 42 

 

 

 

Need Help? Refer to the Quick Guide below

Inheritance is a mechanism where a new class (Derived Class) acquires the properties and behaviors (variables and functions) of an existing class (Base Class).

It enables the "Is-a" Relationship (e.g., a Button is a GPIO_Device).

This allows you to write generic code in a Base class (like Packet) and extend or specialize it in Derived classes (like WiFiPacket, BluetoothPacket) without rewriting the common logic.

Syntax & Usage

1. Basic Declaration

Use the : symbol followed by the access mode (usually public).

// Base Class (Parent)
class SerialPort {
public:
    void open(int baud) { /* Generic open logic */ }
    void close() { /* Generic close logic */ }
};

// Derived Class (Child)
// Syntax: class Child : access_specifier Parent
class UART : public SerialPort {
public:
    // UART inherits open() and close() automatically.
    
    // Adds new specific functionality
    void set_parity(int p) { /* ... */ }
};

UART u;
u.open(9600);    // Calls Base function
u.set_parity(1); // Calls Derived function

2. Access Specifiers (protected)

Inheritance introduces a new access level: protected.

  • private: Visible only to the Base class. (Derived classes cannot see it).
  • protected: Visible to the Base class and Derived classes. (Outsiders cannot see it).
  • public: Visible to everyone.
class Sensor {
protected:
    int raw_adc_value; // Children can access this directly
private:
    int secret_key;    // Children CANNOT access this
};

class TempSensor : public Sensor {
public:
    void read() {
        raw_adc_value = HW_Read(); // ✅ Allowed (protected)
        // secret_key = 0;         // ❌ Error (private)
    }
};

3. Constructor Execution Order

When you create a Derived object, the Base constructor runs first, then the Derived constructor.

When destroyed, the order is reversed (Derived destructor first, then Base).

class Base {
public:
    Base(int x) { /* Init Base */ }
};

class Derived : public Base {
public:
    // Must explicit call Base constructor in initializer list
    Derived(int x, int y) : Base(x) { 
        /* Init Derived */ 
    }
};

Memory Layout

A Derived class object is essentially the Base class object with the new fields "glued" to the end of it. It forms a single contiguous block of memory.

Address OffsetContentBelongs To
0x00Base::var1Base Class
0x04Base::var2Base Class
0x08Derived::new_varDerived Class

Relevance in Embedded/Firmware

1. Hardware Abstraction (HAL)

This is the standard architecture for portable drivers.

  • Base Class: Display (Defines generic drawPixel, drawRect, clear).
  • Derived Class: ILI9341_Display (Implements drawPixel for specific hardware).
  • Application: Writes to Display*. It doesn't care which screen is connected.

2. Generic Protocol Handling

If you have multiple communication packets (Command Packet, Data Packet, Ack Packet) that all share a Header (ID, Length) and CRC, you create a BasePacket class.

  • BasePacket handles CRC calculation and Header parsing.
  • DataPacket adds the payload buffer. This saves Flash memory by not duplicating the CRC logic 3 times.

Common Pitfalls (Practical Tips)

PitfallDetails
❌ Object SlicingIf you assign a Derived object to a Base variable (Base b = derived;), the derived parts are sliced off. Always use Pointers (Base*) or References (Base&) when dealing with hierarchy.
❌ Missing Virtual DestructorIf you delete a Derived object via a Base pointer (Base* b = new Derived(); delete b;), the Derived destructor will NOT run unless the Base destructor is marked virtual. This causes memory leaks.
❌ Multiple InheritanceInheriting from two classes (class C : public A, public B) is possible but dangerous (ambiguity, diamond problem). Avoid it in firmware; use Composition instead.
✅ Composition over InheritanceIf a class "Has a" dependency (e.g., A Car has an Engine), use a member variable, not inheritance. Only use Inheritance for "Is a" relationships (e.g., A Car is a Vehicle).