128. RAII Shutdown Ordering

In embedded systems, hardware components must shut down in a strict order.
If the shutdown sequence is incorrect, peripherals may hang, buses may lock, or clocks may remain enabled unnecessarily.

You are given three system components:

  • Clock – provides the base clock signal
  • Bus – depends on the Clock
  • Driver – depends on the Bus

Your task is to design a system where:

  • All components start automatically when the system is created
  • All components shut down automatically when the system goes out of scope
  • Shutdown happens in the correct dependency order
  • main() does not manually start or stop any component

This must be achieved only using composition and destructors (RAII), without explicit cleanup calls.

What You Must Do

  • Create a class named SystemController
  • SystemController must own:
    • Clock
    • Bus
    • Driver
  • Dependencies must be wired so that:
    • Bus uses Clock
    • Driver uses Bus
  • Do not add any shutdown or cleanup calls in main()
  • Correct shutdown must occur only because objects go out of scope

Program Flow

  1. Read an address and a value from input
  2. Create a SystemController object inside a local scope
  3. All components start automatically
  4. The driver performs one write operation
  5. The scope ends
  6. Driver shuts down
  7. Bus shuts down
  8. Clock shuts down

Input

Two integers provided via standard input:

  • Address
  • Value

Output

The output must match exactly, including order and formatting:

Clock enabled
Bus initialized
Driver started
Bus write: <addr> <value>
Driver stopped
Bus stopped
Clock disabled

Constraints

  • Clock, Bus, and Driver must NOT be created in main()
  • SystemController must be the only owner of all components
  • No manual cleanup calls
  • No dynamic memory allocation
  • No smart pointers
  • No inheritance
  • Output 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.