Composition, RAII & Ownership

cardimg

These three concepts form the backbone of robust C++ architecture.

  1. Composition ("Has-a"): Instead of inheriting from classes ("Is-a"), you build complex objects by combining smaller, simpler objects. A Car has an Engine, it is not an Engine.
  2. RAII (Resource Acquisition Is Initialization): The "Golden Rule" of C++. Resources (Memory, Locks, Files) are acquired in the Constructor and released in the Destructor. This ties resource management to the Scope, guaranteeing cleanup.
  3. Ownership: Defines who is responsible for the lifetime of a resource. "If I own it, I delete it."

Syntax & Usage

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 Model

Ownership TypeConceptC++ 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&).

Relevance in Embedded/Firmware

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.

Common Pitfalls (Practical Tips)

PitfallDetails
❌ Deep InheritanceAvoid 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 (T*) to own data.

drivers.add(new UART()); is a leak waiting to happen. Use unique_ptr for ownership, raw pointers only for observing.

❌ Circular DependencyIf 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.

Motor(PWM& pwm) is better than creating a new PWM inside Motor, because it allows you to pass a MockPWM for testing.

 

 

 

 

Concept understood? Let's apply and learn for real

Practice now