Destructors (Automatic Cleanup)

cardimg

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.

 

 

 

 

 

Concept understood? Let's apply and learn for real

Practice now