125. Driver Resource Composition

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:

  • An internal buffer
  • A hardware register block

Both resources must:

  • Be created automatically when the driver object is created
  • Be destroyed automatically when the driver object is destroyed
  • Never be accessed directly from main()

Do This:

  • Create a Buffer class that stores one integer
  • Create a RegisterBlock class that stores one integer
  • Create a Driver class that owns both objects as direct members
  • Read two integers from input
  • Store the first value in the buffer
  • Store the second value in the register block
  • Print both stored values only through the Driver
  • Observe the exact construction and destruction order when the driver goes out of scope

⚠️ 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:

  1. Read two integers from input
  2. Enter a local scope
  3. Create a Driver object
  4. Driver stores values in its internal resources
  5. Driver prints both stored values
  6. Scope ends
  7. Driver shuts down
  8. Owned resources are destroyed automatically

Input:

Two integers separated by whitespace

Output (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()
  • Access to both resources must occur only through the Driver
  • No dynamic memory allocation
  • No inheritance
  • Member declaration order must enforce construction order
  • Output format and order must match exactly

 

 

 

Need Help? Refer to the Quick Guide below

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.