Classes & Objects

Destructors & Object Lifetime

Why this matters

Module 2 established that a stack variable is destroyed automatically the instant its scope ends. What that actually meant for a plain int was uninteresting – its memory is just reclaimed. For a class object, it means something with real consequences: a special member function, the destructor, runs automatically first. This lesson is about exactly when that happens, and about new/delete, the C++ tool that extends the same guarantee to objects living on the heap.

Declaring a destructor: ~ClassName()

~Logger() {
    std::cout << "Destructing " << name << std::endl;
}

A destructor is a member function named after the class with a ~ in front, taking no parameters and no return type – and unlike constructors, a class can have at most one. It runs automatically, with no explicit call anywhere in your code, at the exact moment an object’s lifetime ends.

Tracing the worked example’s output

Run it and watch the order carefully – it’s the entire point of this lesson:

start of main
Constructing local
inside inner scope
Destructing local          <- the instant the inner { } block ends, not when main ends
after inner scope
Constructing heap
before delete
Destructing heap            <- exactly when delete runs, not before, not automatically later
end of main

local is destroyed the moment its enclosing { } block ends – immediately, deterministically, without waiting for main itself to finish. This is exactly the stack-frame lifetime rule from Module 2, now with a visible, concrete side effect attached to it. heapObj, by contrast, points at an object that will live until you explicitly end its life – which delete does, right where it’s written, not automatically at scope end (the pointer heapObj itself is destroyed when main ends, the same as any local variable, but that only destroys the pointer – a plain address – not the object it was pointing at).

new and delete: C++’s constructor-and-destructor-aware alternative to malloc/free

Logger *heapObj = new Logger("heap"); does two things malloc fundamentally cannot: it allocates enough memory for a Logger, and it calls Logger’s constructor on that memory, with "heap" as the argument – which is exactly why "Constructing heap" prints. malloc(sizeof(Logger)) would hand you back raw, unconstructed memory – no constructor call, name never initialized, and if Logger had a numerator/denominator pair from earlier lessons, quite possibly garbage in both. malloc and free know nothing about constructors or destructors at all; they only know how to reserve and release bytes.

delete heapObj; is the mirror image: it calls Logger’s destructor first (hence "Destructing heap" printing exactly there, not later), then releases the memory – the reverse of what new did, in the reverse order, exactly like the general RAII pattern this previews (full treatment next module). From here on, when you need a single object on the heap, reach for new/delete instead of malloc/free – they’re aware of your class in a way malloc/free simply are not. (Module 2’s rules about matching every allocation with exactly one deallocation, and never using a pointer after freeing it, still apply identically here – only the specific function names change.)

Multiple objects are destroyed in reverse order of construction

One more fact worth having ready: when several stack objects share a scope, they’re destroyed in the exact reverse of the order they were constructed in – the most recently created one is destroyed first, unwinding back to the first. This mirrors how you’d naturally clean up a stack of anything: undo the most recent step first. It’s also exactly why this pattern is safe for objects whose destructors depend on other objects still being alive – the last thing built is the first thing torn down, never the other way around.

Try it
Output will appear here.

Exercises