In embedded systems, a driver often owns multiple internal resources whose lifetimes must be fully controlled and deterministic.
These resources are typically created when the driver is initialized and destroyed automatically when the driver shuts down.
In this task, you will model a driver that owns multiple internal resources using composition, ensuring correct construction and destruction order without dynamic allocation.
The driver owns:
Both resources must:
main()Do This:
Buffer class that stores one integerRegisterBlock class that stores one integerDriver class that owns both objects as direct members⚠️ Important C++ Rule (Required for Correct Output):
Class member objects are constructed in the order they are declared in the class, and destroyed in the reverse order, regardless of constructor body code.
Your solution must rely on this rule to match the required output exactly.
Program Flow:
Driver objectInput:
Two integers separated by whitespaceOutput (Exact Order Required):
Buffer created
RegisterBlock created
Driver initialized
Buffer value: <value1>
Register value: <value2>
Driver destroyed
RegisterBlock destroyed
Buffer destroyed
Constraints:
Buffer and RegisterBlock must not be created in main()
These three concepts form the backbone of robust C++ architecture.
Car has an Engine, it is not an Engine.1. Composition (Building Blocks)
Composition allows you to swap parts easily and keeps dependencies clear.
class PWM {
public:
void set(int duty) { /* ... */ }
};
class Encoder {
public:
int read() { /* ... */ }
};
// Composition: MotorController "Has-a" PWM and "Has-a" Encoder
class MotorController {
PWM pwm_driver; // Sub-component 1
Encoder enc_driver; // Sub-component 2
public:
void set_speed(int speed) {
int current = enc_driver.read(); // Delegate work
pwm_driver.set(speed);
}
};2. RAII (The Scope Guard)
The most common embedded use case is managing interrupts or mutexes.
class ScopedIntLock {
public:
ScopedIntLock() { __disable_irq(); } // Acquire
~ScopedIntLock() { __enable_irq(); } // Release
};
void critical_function() {
ScopedIntLock lock; // Interrupts disabled HERE
// ... do critical work ...
if (error) return; // Safe! Destructor re-enables interrupts automatically.
} // Destructor runs here naturally.| Ownership Type | Concept | C++ Tool |
|---|---|---|
| Owner | "I control this life. When I die, it dies." | std::unique_ptr or Member Variable. |
| Shared Owner | "We keep this alive as long as one of us needs it." | std::shared_ptr. |
| Observer | "I just look at it. I hope it's still there." | Raw Pointer (T*) or Reference (T&). |
1. Composition over Inheritance
Embedded hierarchies can get messy ("Is a SPI driver a Stream? Is it a Bus?").
Composition is cleaner. A Sensor class shouldn't inherit from I2C. It should contain a pointer/reference to an I2C driver. This allows you to easily swap the I2C driver for a SPI driver without rewriting the Sensor class.
2. Leak-Free Error Handling
In C, if an error occurs in the middle of a function, you must remember to free() memory or unlock() mutexes before every return.
With RAII, you just return. The compiler inserts the cleanup code for you (Stack Unwinding), making firmware crash-proof.
3. Clear Lifetimes
By using Composition and RAII, you avoid "dangling pointers" where a driver tries to access a hardware object that has already been de-initialized. If the parent object (Robot) dies, its children (Motors) are destroyed automatically and in the correct order.
| Pitfall | Details |
|---|---|
| ❌ Deep Inheritance | Avoid creating 5-layer deep class hierarchies (Driver -> Serial -> UART -> STM32_UART). It increases code size (v-tables) and makes debugging a nightmare. Prefer Composition. |
| ❌ Raw Pointers as Owners | Never use a raw pointer (
|
| ❌ Circular Dependency | If Object A owns B, and Object B owns A (via shared_ptr), they will keep each other alive forever (Memory Leak). Use weak_ptr or raw references to break the cycle. |
| ✅ Dependency Injection | Pass the "Sub-components" into the Constructor rather than creating them inside.
|