Destructors
A constructor initializes an object when its lifetime begins, but resources may also need cleanup when that lifetime ends. In this lesson, you will learn how C++ destructors work, when they execute, how destruction order works, and how destructors support safe resource management.
Introduction
In the previous lesson, you learned about constructors, which participate in initializing objects when their lifetimes begin.
Objects can also acquire resources while they exist. A file object may open a file, a network object may own a socket, and a class may manage dynamically allocated memory.
When the lifetime of such an object ends, the resources it owns may need to be released correctly. C++ provides destructors for this purpose.
Beginning of Lifetime
- Object initialization begins.
- Constructor is selected.
- Initial state is established.
- Object becomes ready to use.
End of Lifetime
- Object lifetime ends.
- Destructor executes.
- Owned resources can be released.
- Object destruction completes.
Constructors help establish an object’s state at the beginning of its lifetime, while destructors provide a place for cleanup when that lifetime ends.
What is a Destructor?
A destructor is a special non-static member function that is invoked when an object of a class type is destroyed.
Its purpose is to perform any cleanup required before the object’s lifetime ends completely. This may include releasing resources directly owned by the object.
class Student
{
public:
~Student()
{
// Cleanup code
}
};Object Exists
│
▼
Lifetime Ends
│
▼
Destructor Executes
│
▼
Cleanup Completes
│
▼
Object Destroyed| Feature | Destructor |
|---|---|
| Purpose | Perform cleanup during destruction |
| Name | Class name prefixed with ~ |
| Return Type | None |
| Parameters | None |
| Overloading | Not allowed |
| Invocation | Occurs as part of object destruction |
A destructor is the special member function associated with the end of an object’s lifetime.
Why Do We Need Destructors?
Many objects manage resources that must be released when those objects are destroyed. Destructors allow cleanup logic to be tied directly to object lifetime.
Without Proper Cleanup
- Owned resources may remain unreleased.
- Dynamically allocated memory may leak.
- Files may remain open longer than intended.
- System handles may be exhausted.
- Manual cleanup may be forgotten.
With Lifetime-Based Cleanup
- Cleanup occurs when object lifetime ends.
- Owned resources can be released reliably.
- Manual cleanup calls are reduced.
- Resource ownership becomes easier to manage.
- Code becomes more exception-safe.
Memory
Release dynamically allocated memory owned by an object.
Files
Close file resources when their owner is destroyed.
Network Resources
Release sockets and related system handles.
Connections
Release managed database or service resources.
Locks
Release ownership of synchronization resources.
Custom Resources
Perform cleanup required by application-specific resources.
A destructor does not automatically solve every resource leak. Cleanup works correctly only when resource ownership is designed correctly. Modern C++ usually prefers RAII types such as std::vector, std::string, std::unique_ptr, and file-stream objects instead of manual resource management.
Real-World Analogy
Imagine staying in a hotel room. During your stay, you use the room and its facilities. When your stay ends, checkout procedures complete the lifecycle of that reservation.
A destructor plays a similar role for an object. When the object’s lifetime ends, the destructor provides a final opportunity to release resources owned by that object.
| Hotel Analogy | C++ |
|---|---|
| Reservation begins | Object lifetime begins |
| Check-in setup | Constructor |
| Hotel stay | Object in use |
| Checkout procedure | Destructor |
| Stay ends | Object lifetime ends |
🏨 Check In
│
▼
🛏️ Use Room
│
▼
🧹 Checkout
│
▼
🔑 Return Resources
│
▼
🚪 Stay EndsCharacteristics of Destructors
Tilde Prefix
The destructor name begins with the ~ symbol.
Class Name
The remaining name matches the class name.
No Return Type
A destructor has no return type, not even void.
No Parameters
A destructor does not accept parameters.
1️⃣ One Per Class
A class can have only one destructor.
Automatic Destruction
It is invoked as part of object destruction.
Key Characteristics
- The destructor name begins with a tilde (~).
- The name after the tilde matches the class name.
- A destructor has no return type.
- A destructor accepts no parameters.
- A destructor cannot be overloaded.
- A class can have only one destructor.
- The destructor participates in object destruction.
- A destructor may be virtual.
- A destructor may be implicitly generated by the compiler.
class Student
{
public:
Student() // Constructor
{
}
~Student() // Destructor
{
}
};Unlike constructors, destructors cannot be overloaded because they have no parameter lists that could distinguish multiple versions.
Destructor Syntax
A destructor is declared using a tilde followed by the class name.
class ClassName
{
public:
~ClassName()
{
// Cleanup code
}
};class Student
{
public:
~Student()
{
// Cleanup code
}
};| Part | Meaning |
|---|---|
| ~ | Identifies the function as a destructor |
| Student | Must match the class name |
| () | Destructor accepts no parameters |
| { } | Destructor body |
| public: | Allows normal destruction from accessible contexts |
// ❌ Incorrect
void ~Student()
{
}
// ❌ Incorrect
~Student(int id)
{
}
// ✅ Correct
~Student()
{
}A destructor has no return type and accepts no parameters.
When is a Destructor Called?
A destructor is invoked when the lifetime of an object ends. The exact moment depends on the object’s storage duration and how it was created.
Local Object
Destroyed when execution leaves its scope.
Dynamic Object
Destroyed when delete is applied to the owning pointer.
Static Object
Destroyed during program termination according to lifetime rules.
Temporary Object
Destroyed according to temporary object lifetime rules.
Container Element
Destroyed when the container removes or destroys the element.
Member Object
Destroyed automatically as part of containing-object destruction.
void run()
{
Student student1;
// student1 exists here
}
// student1 is destroyed hereStudent* student1 = new Student;
delete student1;
// Destructor runs as part of deleteUse delete only for objects created by a matching new expression. Modern C++ generally prefers automatic objects and smart pointers instead of direct new and delete.
Example 1: Simple Destructor
This example shows the relationship between construction and destruction for a local object.
#include <iostream>
class Student
{
public:
Student()
{
std::cout << "Constructor Called"
<< std::endl;
}
~Student()
{
std::cout << "Destructor Called";
}
};
int main()
{
Student student1;
return 0;
}Execution Order
- Execution enters main().
- student1 begins its lifetime.
- The Student constructor executes.
- The body of main() continues.
- Execution leaves main().
- student1 reaches the end of its lifetime.
- The Student destructor executes.
Constructor Called
Destructor CalledFor a normal local object, no explicit destructor call is needed when the object leaves scope.
Object Lifetime & LIFO Order
Every object has a lifetime. For local objects created in the same scope, destruction normally occurs in the reverse order of successful construction.
Student student1;
Student student2;
Student student3;Construction
────────────
1. student1
2. student2
3. student3
Destruction
───────────
1. student3
2. student2
3. student1Construction Order
- student1
- student2
- student3
Destruction Order
- student3
- student2
- student1
For automatic objects in the same scope, objects are destroyed in the reverse order of their completed construction.
Do not treat reverse destruction order as a universal rule for every object in a program. Destruction depends on storage duration, ownership, scope, container behavior, inheritance, and other lifetime rules.
Example 2: Multiple Objects
Using an identifier for each object makes the reverse destruction order easier to observe.
#include <iostream>
class Student
{
private:
int id;
public:
Student(int studentId)
: id(studentId)
{
std::cout
<< "Constructing Student "
<< id
<< std::endl;
}
~Student()
{
std::cout
<< "Destroying Student "
<< id
<< std::endl;
}
};
int main()
{
Student student1(1);
Student student2(2);
return 0;
}Execution
- student1 is constructed first.
- student2 is constructed second.
- The end of main() is reached.
- student2 is destroyed first.
- student1 is destroyed last.
Constructing Student 1
Constructing Student 2
Destroying Student 2
Destroying Student 1Create
──────
student1
│
▼
student2
Destroy
───────
student2
│
▼
student1Printing object identifiers makes construction and destruction order much easier to understand than printing identical messages.
Example 3: Releasing Resources
The most important use of destructors is lifetime-based resource management. A resource should be acquired by an object and released when that object is destroyed.
#include <fstream>
#include <iostream>
class LogFile
{
private:
std::ofstream file;
public:
LogFile(const std::string& filename)
: file(filename)
{
std::cout << "File resource acquired"
<< std::endl;
}
~LogFile()
{
std::cout << "LogFile object destroyed";
}
void write(const std::string& message)
{
file << message;
}
};
int main()
{
LogFile log("app.log");
log.write("Application started");
return 0;
}The std::ofstream member manages its own file resource. When the LogFile object is destroyed, its members are also destroyed automatically, and the stream releases its file resource.
Prefer resource-managing types whose destructors already perform cleanup. This reduces the need to write manual cleanup code.
class Number
{
private:
int* value;
public:
Number(int n)
: value(new int(n))
{
}
~Number()
{
delete value;
}
};A class that directly owns a raw pointer usually also needs correct copy and move behavior. Modern C++ generally prefers standard containers or smart pointers instead.
Constructor vs Destructor
Constructors and destructors represent opposite ends of an object’s lifetime.
| Feature | Constructor | Destructor |
|---|---|---|
| Purpose | Initialize object | Perform destruction cleanup |
| When Used | Beginning of lifetime | End of lifetime |
| Name | Same as class | Class name prefixed with ~ |
| Return Type | None | None |
| Parameters | Can accept parameters | Cannot accept parameters |
| Overloading | Allowed | Not allowed |
| Number Per Class | Multiple possible | Only one |
| Typical Responsibility | Establish state | Release owned resources |
Constructor
- Begins object initialization.
- Can accept arguments.
- Can be overloaded.
- Establishes initial state.
- May acquire resources.
Destructor
- Participates in object destruction.
- Accepts no arguments.
- Cannot be overloaded.
- Performs final cleanup.
- May release owned resources.
Construction establishes an object, the object performs its work, and destruction completes its lifetime.
Real-World Applications
Destruction-based cleanup appears throughout C++ systems because many resources have clear ownership and release requirements.
File Handling
Release file handles when file-managing objects are destroyed.
Dynamic Memory
Release owned memory through containers or smart pointers.
Database Systems
Release connection, transaction, or statement resources.
Network Programming
Release sockets and communication handles.
Game Development
Release graphics, audio, and engine resources.
Synchronization
Release locks automatically through scope-based guard objects.
{
std::lock_guard<std::mutex> guard(mutex);
// Protected work
}
// guard is destroyed here
// The mutex is unlocked automaticallyAcquire Resource
│
▼
Object Owns Resource
│
▼
Use Resource
│
▼
Object Destroyed
│
▼
Release ResourceAdvantages of Destructors
Automatic Cleanup
Cleanup can occur automatically when object lifetime ends.
Lifetime Binding
Resource lifetime can be tied to object lifetime.
Reliability
Cleanup is less likely to be forgotten.
Exception Safety
Automatic objects are still destroyed during stack unwinding.
Encapsulation
Cleanup details remain inside the owning class.
RAII Support
Destructors are central to Resource Acquisition Is Initialization.
- Tie cleanup to object lifetime.
- Reduce forgotten manual cleanup.
- Support reliable release of owned resources.
- Improve exception safety through automatic destruction.
- Keep resource management inside the responsible class.
- Enable RAII-based design.
- Work naturally with scopes and ownership.
Resource Acquisition Is Initialization means that a resource is owned by an object whose lifetime controls acquisition and release. The destructor performs or triggers the release when the object is destroyed.
Common Beginner Mistakes
Explicit destructor calls are rarely appropriate and can lead to lifetime errors if the object is later destroyed again.
Destructors do not accept parameters.
Destructors have no return type, not even void.
A class can have only one destructor.
delete is only for suitable dynamically allocated objects created through matching new expressions.
Prefer standard containers and smart pointers when they can own the resource safely.
Student student1;
// ❌ Usually incorrect
// student1.~Student();
// Let normal lifetime rules destroy it automatically.// ❌ Incorrect
// ~Student(int id)
// {
// }
// ✅ Correct
~Student()
{
}// ❌ Incorrect
// void ~Student()
// {
// }
// ✅ Correct
~Student()
{
}Student student1;
// ❌ Incorrect
// delete &student1;Explicitly invoking a destructor does not automatically prevent normal destruction later. Incorrect use can cause an object to be destroyed twice or used after its lifetime has ended.
Best Practices
- Prefer RAII for resource management.
- Prefer standard containers and smart pointers over owning raw pointers.
- Keep destructors focused on cleanup.
- Do not allow exceptions to escape from destructors.
- Avoid manual destructor calls in ordinary application code.
- Use virtual destructors for polymorphic base classes that may be deleted through base pointers.
- Prefer the Rule of Zero when standard resource-managing members can handle ownership.
- If a class manually manages a resource, consider the Rule of Five.
- Make ownership responsibilities clear.
- Keep destruction predictable and efficient.
#include <memory>
class Number
{
private:
std::unique_ptr<int> value;
public:
Number(int n)
: value(std::make_unique<int>(n))
{
}
// No custom destructor required
};class Base
{
public:
virtual ~Base() = default;
};
class Derived : public Base
{
};When a class uses resource-managing members such as std::string, std::vector, and std::unique_ptr, it often does not need to define its own destructor, copy constructor, or move operations.
If objects of derived classes may be deleted through base-class pointers, the base class should generally have a virtual destructor.
Frequently Asked Questions
What is a destructor?
A destructor is a special member function invoked when an object of a class type is destroyed.
When is a destructor called?
It is invoked when the object’s lifetime ends according to its storage duration and ownership rules.
Can a destructor have parameters?
No. A destructor accepts no parameters.
Does a destructor have a return type?
No. A destructor has no return type, not even void.
Can a class have multiple destructors?
No. A class can have only one destructor.
Can destructors be overloaded?
No. Destructors cannot be overloaded.
Are objects always destroyed in LIFO order?
Automatic objects in the same scope are destroyed in reverse order of successful construction, but destruction rules differ for other storage durations and ownership models.
Should I call a destructor manually?
Usually no. Normal scope and ownership mechanisms should control destruction. Explicit destructor calls are reserved for specialized low-level lifetime management.
Does a destructor automatically free every pointer?
No. A raw pointer does not express ownership by itself. The class must manage ownership correctly, preferably through containers or smart pointers.
What is RAII?
RAII is a C++ design technique that ties resource ownership to object lifetime so resources are released automatically during destruction.
Why might a base class need a virtual destructor?
A virtual destructor allows correct destruction when a derived object is deleted through a pointer to a polymorphic base class.
Do I always need to write a destructor?
No. In modern C++, many classes should use the Rule of Zero and rely on their resource-managing members to perform cleanup automatically.
Key Takeaways
- A destructor is a special member function associated with object destruction.
- The destructor name begins with a tilde (~) followed by the class name.
- Destructors have no return type.
- Destructors accept no parameters.
- A class can have only one destructor.
- Destructors cannot be overloaded.
- Local objects are destroyed when their scope ends.
- Automatic objects in the same scope are destroyed in reverse construction order.
- Destructors are central to lifetime-based resource management.
- RAII ties resource ownership to object lifetime.
- Modern C++ prefers containers and smart pointers over manual resource management.
- Manual destructor calls are rarely appropriate.
- Polymorphic base classes often require virtual destructors.
- Many modern classes should follow the Rule of Zero.
Summary
Destructors are special member functions that participate in object destruction. They provide a place to perform cleanup when an object’s lifetime ends.
A destructor uses the class name prefixed with a tilde, has no return type, accepts no parameters, and cannot be overloaded.
For automatic objects in the same scope, destruction occurs in the reverse order of successful construction. Other objects follow the lifetime rules associated with their storage duration and ownership.
Destructors are especially important for RAII, where resources are owned by objects and released automatically when those objects are destroyed.
Modern C++ usually prefers standard containers, smart pointers, streams, and other resource-managing types. These types often eliminate the need to write custom destructors.
Now that you understand how object lifetimes begin with construction and end with destruction, the next lesson will introduce encapsulation, which controls how an object’s internal data and behavior are accessed.