Question.3
A developer uses raw pointers to own drivers:
class System {
IDriver* driver;
public:
System(int type) {
driver = new UART_Driver();
}
// No destructor!
};What is wrong?
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.
|