In embedded firmware, drivers are commonly layered to separate responsibilities and ensure correct hardware bring-up.
A typical structure looks like:
In C++, multilevel inheritance guarantees a strict constructor execution order, which is critical in such designs.
Your task is to implement and verify constructor execution order in a three-level inheritance hierarchy that mimics a real firmware driver stack.
Scenario
You must implement the following class hierarchy:
CoreDriver
↓
CommDriver
↓
SpiDriver
Each class represents a firmware layer and performs initialization in its constructor.
Requirements
Step 1: CoreDriver
CoreDriverProvide a constructor that prints exactly:
Core driver initialized
Step 2: CommDriver
CommDriverCoreDriverProvide a constructor that prints exactly:
Comm driver initialized
Step 3: SpiDriver
SpiDriverCommDriverspeedPrints exactly:
SPI driver initialized
speed value internallyProvide a member function that prints:
SPI speed <speed>
Input
One integer value:
speed
Program Flow (Mandatory Order)
speed from standard inputSpiDriver object using speedCoreDriverCommDriverSpiDriver
Example Input
8
Example Output
Core driver initialized
Comm driver initialized
SPI driver initialized
SPI speed 8
Constraints (Strict)
new, malloc)
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). |