In embedded firmware development, drivers often support multiple independent capabilities such as power management and interrupt handling.
One common approach is to use multiple inheritance to compose these capabilities.
However, when multiple base classes expose functions with the same name, calling those functions from the derived class can lead to compile-time ambiguity.
This problem focuses on correctly resolving such ambiguity using standard C++ techniques that are safe and commonly used in firmware codebases.
Scenario
You are given two capability-style base classes:
enable()enable()A driver class:
PowerControl and InterruptControl.Because both base classes define a function named enable(), calling enable() directly on a SpiDriver object is ambiguous and will not compile.
Objective
Modify the program so that:
enable() function is called based on input
Rules (Strict)
You must follow all rules below:
Input
One integer value:
modeWhere:
0 → Enable power control1 → Enable interrupt controlProgram Flow (Mandatory Order)
modeSpiDriver objectmode == 0:mode == 1:Output
If mode == 0:
Power enabled If mode == 1:
Interrupt enabled
Example 1
Input:
0Output:
Power enabled
Example 2
Input:
1Output:
Interrupt enabled
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.
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 function2. 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 */
}
};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 Offset | Content | Belongs To |
|---|---|---|
0x00 | Base::var1 | Base Class |
0x04 | Base::var2 | Base Class |
0x08 | Derived::new_var | Derived Class |
1. Hardware Abstraction (HAL)
This is the standard architecture for portable drivers.
Display (Defines generic drawPixel, drawRect, clear).ILI9341_Display (Implements drawPixel for specific hardware).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.| Pitfall | Details |
|---|---|
| ❌ Object Slicing | If 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 Destructor | If 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 Inheritance | Inheriting 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 Inheritance | If 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). |