C++ Destructors
Lesson outline
Introduction to Destructors
Destructors in C++ are special member functions that are automatically called when an object's lifetime ends (e.g., it goes out of scope) or when it is explicitly deleted. They perform cleanup operations and release resources acquired during the object's lifetime.
Destructors are essential for preventing resource leaks and ensuring proper RAII (Resource Acquisition Is Initialization) in C++ programs.
Destructor Characteristics
- Destructors are named as the class name prefixed with a tilde (
~Class). - They cannot take parameters or return values, and a class can have only one destructor.
- They are automatically invoked when objects are destroyed; destruction order is the reverse of construction within an object.
- If you do not declare a destructor, one is implicitly declared. It may be implicitly defined as defaulted or as deleted depending on the destructibility of bases/members.
- Destructors are implicitly
noexcept(cannot throw) unless a member/base destructor may throw. Never let exceptions escape a destructor.
Basic Destructor Example
This example shows a simple class with a destructor that prints a message when the object is destroyed.
Destructor with Dynamic Memory (Rule of Five safe)
When managing raw dynamic memory, implement proper special member functions to avoid double frees and leaks (Rule of Five). Prefer standard containers or smart pointers when possible.
Polymorphism & Virtual Destructors
When deleting through a base-class pointer, the base must have a virtual destructor so the derived destructor runs.
Without a virtual destructor, deleting a derived object via a base pointer calls only the base destructor → resource leaks.
Pure Virtual Destructors
An abstract base class can declare a pure virtual destructor to enforce abstractness, but it still must provide a definition (body).
Exceptions and Destructors
Do not let exceptions escape from a destructor. During stack unwinding from another exception, throwing again calls std::terminate().
If cleanup can fail, provide a separate close()/flush() member that reports errors; keep the destructor noexcept.
delete vs delete[]
delete must match new, and delete[] must match new[]. Using the wrong form is undefined behavior. delete[] calls the destructor for each element in the array.
Best Practices
- Define a destructor if your class manages resources (files, sockets, mutexes, memory). Better: wrap resources in RAII types (
std::unique_ptr,std::vector, file/handle wrappers). - Mark base-class destructors
virtualwhen the class is intended to be used polymorphically. - Follow the Rule of Three/Five/Zero: if you need any of destructor/copy/move operations, you likely need the others—or better, design so you need none (Rule of Zero).
- Keep destructors
noexcept; handle failures via explicit functions. - Avoid long/complex logic in destructors; keep them focused on releasing resources.
Summary Table
| Scenario | Destructor Behavior |
|---|---|
| Object goes out of scope | Destructor automatically called |
delete p used | Destructor called, then memory freed |
delete[] p used for arrays | Destructor called for each element, then memory freed |
| Exception thrown | Destructors run for all fully-constructed local objects during stack unwinding |
| Virtual destructor on base | Ensures proper derived cleanup when deleting via base pointer |