What a Destructor Is
On the previous page you saw constructors - special functions that run when an object is born to set up its initial state. A destructor is the mirror image: a special function that runs when an object dies, to clean up after it.
You declare one with the class name prefixed by a tilde (~). It takes no parameters, returns nothing, and a class can have exactly one of them. You almost never call it by hand - C++ calls it for you at the right moment.
Notice the destructor message prints after main finishes its body but before the program exits. When log goes out of scope at the closing brace, C++ runs ~Logger() for you.
When Destructors Run
The exact timing depends on where the object lives:
- Stack (local) objects are destroyed when they go out of scope - at the closing
}of the block. - Heap objects (created with
new) are destroyed when you calldelete. If you forget thedelete, the destructor never runs and you leak.
This example makes the difference visible:
Objects are destroyed in reverse order of construction. a was built first, so it dies last. This LIFO (last-in, first-out) order matters when objects depend on each other.
Why Destructors Matter: RAII
The real power of destructors is that they make cleanup automatic and exception-safe. Instead of remembering to release a resource on every code path, you put the release in a destructor and let the language guarantee it runs. This pattern is called RAII - Resource Acquisition Is Initialization - and it's the backbone of modern C++.
Here a class owns a heap buffer: it allocates in the constructor and frees in the destructor, so callers never touch new/delete themselves.
The key insight: even if an exception were thrown after squares was created, the stack would unwind and ~IntArray() would still run. That guarantee is why RAII is so reliable - and why you rarely write a bare delete in good C++ code.
The Rule of Three (and Five)
A class with a custom destructor almost always owns a raw resource, and that creates a hidden danger. The compiler-generated copy constructor and copy assignment do a shallow copy - they copy the pointer, not the buffer it points to. Now two objects hold the same pointer, and both destructors will delete it, causing a double-free crash.
IntArray a(5);
IntArray b = a; // shallow copy: a.data and b.data are the SAME pointer
// at scope end: b's destructor frees the buffer,
// then a's destructor frees it AGAIN -> undefined behavior (double free)
This leads to the Rule of Three: if you write any one of the destructor, copy constructor, or copy assignment operator, you almost certainly need all three. In C++11 and later it extends to the Rule of Five, adding the move constructor and move assignment.
There's an even better rule, though - the Rule of Zero: design classes so you don't manage raw resources at all. Hold a std::vector, std::string, or a smart pointer instead, and the compiler-generated destructor does the right thing for free.
Reach for the Rule of Zero by default. Write a custom destructor only when you truly own a raw resource that no standard type wraps for you.
Virtual Destructors
When you delete an object through a base-class pointer, the destructor must be virtual - otherwise only the base part is destroyed and the derived part leaks. This is one of the most common bugs in polymorphic code, and the compiler won't warn you about it by default.
Without virtual on ~Base, delete p would call only ~Base() - undefined behavior, and the Derived part of the object never gets cleaned up. Rule of thumb: any class with virtual functions (a polymorphic base class) needs a virtual destructor. You'll see exactly why this matters once you start deriving classes.
Common Mistakes and Gotchas
A few traps trip up almost everyone:
Mismatched new/delete. If you allocate with new[], free with delete[]. Mixing new[] with plain delete (or vice versa) is undefined behavior.
Forgetting virtual on a base destructor. As above, deleting a derived object through a base pointer without a virtual destructor leaks the derived part. If you're writing a class meant to be inherited from, make the destructor virtual.
Letting exceptions escape a destructor. A destructor that throws during stack unwinding terminates your program. Destructors are implicitly noexcept in modern C++ - keep cleanup code from throwing, or swallow the exception inside the destructor.
Writing a destructor you don't need. If your members already clean themselves up, an empty ~ClassName() {} adds noise and can quietly disable move operations. When there's nothing to clean up, write no destructor at all.
Next: Inheritance
You've now seen the full lifecycle of an object - constructors bring it to life, destructors clean it up, and virtual destructors keep that cleanup correct when one class builds on another. That last point is a preview of the next big idea: inheritance, where a class reuses and extends the data and behavior of another. The next page shows how to derive one class from another, how construction and destruction chain through the hierarchy, and how the pieces you just learned fit together.
Frequently Asked Questions
What is a destructor in C++?
A destructor is a special member function named ~ClassName() that runs automatically when an object is destroyed - when it goes out of scope or you delete it. Its job is cleanup: releasing memory, closing files, or freeing any resource the object owns. It takes no parameters and has no return type, and a class can have only one.
When does a destructor run in C++?
For a local (stack) object, the destructor runs when it goes out of scope, at the closing }. For a heap object created with new, it runs when you call delete. Members and bases are destroyed automatically afterward, in reverse order of construction.
Do I always need to write a destructor in C++?
No. If your class only holds members that clean up after themselves (like std::string, std::vector, or smart pointers), the compiler-generated destructor is enough - don't write one. You only need a custom destructor when your class owns a raw resource, such as memory from new or an open file handle.