101. LED RAII Controller

Create a class LEDController that simulates controlling an LED on a microcontroller using RAII (Resource Acquisition Is Initialization) principles.

When an object of this class is created, the LED must turn ON.
When the object is destroyed (goes out of scope), the LED must turn OFF automatically.

The LED state must be stored in a static class member so that it can be read even after the object has been destroyed.

Class Requirements:

  • A private static integer state
    • 0 → LED OFF
    • 1 → LED ON
    • Initial value must be 0 (LED OFF)
  • Constructor
    • Sets state = 1 (LED ON)
  • Destructor
    • Sets state = 0 (LED OFF)
  • A static member function print() that outputs exactly:
    • LED=<state>
      

Program Behavior:

  • Read an integer x from standard input.
  • Input Constraint:
    x will be either 0 or 1 only.
    • 0 → do not print LED state inside the scope
    • 1 → print LED state inside the scope
  • Inside a scoped block { ... }:
    • Create a LEDController object.
    • If x == 1, print the LED state inside the block.
  • After the scoped block ends:
    • Print the LED state again.

The final print must reflect the destructor turning the LED OFF.

Example Timeline:

  • Enter block → constructor → LED ON
  • Exit block → destructor → LED OFF

 

Example 1

Input:

1

Output:

LED=1 LED=0 

 

Example 2

Input:

0

Output:

LED=0 

 

Explanation:

  • When x == 1, the LED state is printed while the object is alive inside the scope.
  • When x == 0, the LED state is not printed inside the scope.
  • After the scope ends, the object is destroyed and the LED is guaranteed to be OFF.
  • The final print always reflects the LED OFF state.

 

Constraints:

  • x{0, 1}
  • LED must always be OFF after object destruction.
  • LED state must be stored using a static member variable.
  • Output formatting must match exactly.

 

 

 

 

Need Help? Refer to the Quick Guide below

A Destructor is a special member function that runs automatically when an object goes out of scope (e.g., function returns, block ends) or is explicitly deleted.

Its purpose is to release resources (memory, hardware locks, file handles) held by the object.

It has the same name as the class prefixed with a tilde (~), takes no arguments, and has no return type.

Syntax & Usage

1. Basic Destructor

Cleaning up when a "Scope" ends.

class LED {
    int pin;
public:
    LED(int p) : pin(p) { 
        gpio_write(pin, HIGH); // Turn ON when created
    }

    // Destructor
    ~LED() { 
        gpio_write(pin, LOW);  // Turn OFF when destroyed
    }
};

void blink() {
    LED status_led(13); // Constructor: LED turns ON
    delay(100);
} // End of scope: Destructor runs -> LED turns OFF automatically

2. RAII (The Scope Lock Pattern)

This is the most critical use case in firmware. It guarantees a resource is released even if you return early.

class MutexLock {
    Mutex& mtx;
public:
    MutexLock(Mutex& m) : mtx(m) { mtx.lock(); }   // Acquire
    ~MutexLock() { mtx.unlock(); }                 // Release
};

void critical_function() {
    MutexLock lock(system_mutex); // Mutex locked here

    if (error_detected) return;   // ✅ Safe! Destructor unlocks mutex automatically.
    
    process_data();
} // Destructor unlocks mutex here normally.

Lifecycle Visualization

EventAction
void func() {Start of scope.
Obj x;Constructor runs (Resource acquired).
...Code executes.
}End of scope. Destructor runs (Resource released).

Relevance in Embedded/Firmware

1. Resource Safety (RAII)

In C, if you forget to call Unlock() or Disable() before a return statement, your system deadlocks. Destructors automate this.

  • Interrupts: Disable in Constructor, Enable in Destructor.
  • Power: Wake up peripheral in Constructor, Sleep in Destructor.

2. Virtual Destructors (Polymorphism)

If you use Interfaces (Abstract Classes) in your HAL, you must make the destructor virtual.

If you don't, deleting a derived driver through a base pointer will not call the derived destructor, causing resource leaks (e.g., UART not turning off).

class IDriver {
public:
    virtual ~IDriver() {} // ✅ Essential for Interfaces
};

Common Pitfalls (Practical Tips)

PitfallDetails
❌ Virtual DestructorIf a class has virtual functions, it must have a virtual destructor. Otherwise, cleaning up via a Base pointer (Base* b = new Derived(); delete b;) will fail to clean up the Derived part.
❌ ExceptionsNever throw an exception inside a destructor. If a destructor crashes while another exception is being handled, the program terminates immediately.
❌ Manual CallNever call a destructor manually (obj.~Class()) unless you used Placement New. The compiler calls it for you. Calling it twice causes undefined behavior (double-free).
✅ Order of Destruction

Objects are destroyed in the reverse order of creation.

Lock a; Lock b;b is destroyed first, then a. This is crucial for nested locks.