Destructors are a crucial aspect of C++ programming, responsible for cleaning up resources and performing necessary cleanup operations when object goes out of scope.
What Is a Destructor?
A destructor is a special member function in C++ that is called automatically when an object is about to be destroyed or deallocated. Its primary purpose is to release resources, such as memory, that were allocated during the object's lifetime.
Its primary role is to:
- Release dynamically allocated memory
- Close file handles
- Free system or hardware resources
Syntax of Destructors
In C++, the syntax for a destructor is straightforward. It has the same name as the class, preceded by a tilde (~
). Here's an example:
class MyClass {
public:
// Constructor
MyClass() {
// Initialization code
}
// Destructor
~MyClass() {
// Cleanup code
}
};
Destructor Naming Rules
Like constructors, destructors have specific naming rules:
Rule | Description |
---|---|
Name | Same as the class name, prefixed with a tilde ~ |
Parameters | None allowed (no overloading) |
Return Type | None (not even void ) |
Quantity | Only one destructor per class allowed |
Automatic Invocation of Destructors
Destructors are automatically invoked:
- When a local object goes out of scope
- When an object is explicitly deleted via
delete
ordelete[]
Example 1: Stack Object
void someFunction() {
MyClass obj; // Constructor called
} // Destructor automatically called here
Example 2: Heap Object
void anotherFunction() {
MyClass* obj = new MyClass();
delete obj; // Destructor explicitly invoked
}
Order of Destructor Calls:
C++ ensures destructors are called in the reverse order of construction to properly manage dependencies.
Rules of Order:
- Reverse of construction (Last Constructed → First Destroyed)
- Derived to Base (In inheritance)
- Reverse of member declaration (In composition)
This ensures that dependencies between objects are handled correctly during destruction.
Example:
#include <iostream>
using namespace std;
class A {
public:
~A() { cout << "Destructor A\n"; }
};
class B {
public:
~B() { cout << "Destructor B\n"; }
};
int main() {
A a;
B b;
}
Destructor B
Destructor A
➡️ Because b
was constructed aftera
, it is destroyed beforea
.
Example: Composition (Members)
class X { public: ~X() { cout << "Destructor X\n"; } };
class Y { public: ~Y() { cout << "Destructor Y\n"; } };
class Container {
X x;
Y y;
public:
~Container() { cout << "Destructor Container\n"; }
};
int main() {
Container c;
}
Destructor Container
Destructor Y
Destructor X
➡️ Members are destroyed in reverse order of declaration: y
before x
.
Resource Management in Destructors
Destructors are essential for RAII (Resource Acquisition Is Initialization), ensuring that every acquired resource is released when the object is destroyed.
Example: Dynamic Memory Cleanup
class ResourceHolder {
private:
int* dynamicArray;
public:
// Constructor
ResourceHolder() {
dynamicArray = new int[10];
// Initialization code
}
// Destructor
~ResourceHolder() {
delete[] dynamicArray; // Release dynamically allocated memory
// Cleanup code
}
};
Destructors in Inheritance
Proper Order in Inheritance
- Derived class destructor runs first
- Then base class destructor
Non-virtual Base Destructor (Problem):
If you are using base class pointers:
#include <iostream>
using namespace std;
class Base {
public:
~Base() { cout << "Destructor Base\n"; }
};
class Derived : public Base {
public:
~Derived() { cout << "Destructor Derived\n"; }
};
int main() {
Base* b = new Derived;
delete b; // Only Base destructor is called (undefined behavior)
}
❗Wrong Output:
Destructor Base
This leads to undefined behavior unless the destructor in Base
is virtual.
Virtual Base Destructor (Solution):
#include <iostream>
using namespace std;
class Base {
public:
virtual ~Base() { cout << "Destructor Base\n"; }
};
class Derived : public Base {
public:
~Derived() { cout << "Destructor Derived\n"; }
};
int main() {
Base* b = new Derived;
delete b; // Only Base destructor is called (undefined behavior)
}
Now the output becomes:
Destructor Derived
Destructor Base
➡️ Always use virtual destructors in polymorphic base classes.
Destructor in Abstract Classes
If you're designing an interface (a class intended to be used polymorphically), the destructor must be virtual. Otherwise, deleting a derived object through a base pointer results in undefined behavior — just like in any polymorphic base class.
Example: Without Virtual Destructor
#include <iostream>
using namespace std;
class IShape {
public:
virtual void draw() = 0;
~IShape() { cout << "Destructor IShape\n"; } // Not virtual
};
class Circle : public IShape {
public:
void draw() override { cout << "Drawing Circle\n"; }
~Circle() { cout << "Destructor Circle\n"; }
};
int main() {
IShape* shape = new Circle;
delete shape; // ❌ Undefined behavior
}
Output:
Destructor IShape
➡️ Destructor Circle
is never called – memory leak and undefined behavior!
Example: With Virtual Destructor
class IShape {
public:
virtual void draw() = 0;
virtual ~IShape() { cout << "Destructor IShape\n"; }
};
class Circle : public IShape {
public:
void draw() override { cout << "Drawing Circle\n"; }
~Circle() { cout << "Destructor Circle\n"; }
};
int main() {
IShape* shape = new Circle;
delete shape; // ✔ Proper destruction
}
Output:
Destructor Circle
Destructor IShape
Virtual Destructors — When & Why?
Use virtual ~Base()
when:
- Your class is a base class.
- You might delete a derived object through a base pointer.
Rule of Thumb:
"If your class has virtual functions, its destructor should be virtual too."
Summary Table
Scenario | Destructor Called? | Notes |
---|---|---|
Local (stack) object | ✔ Yes | Automatically at end of scope |
Heap object (with delete ) | ✔ Yes | Must manually delete |
Heap object (no delete ) | ❌ No | Memory leak risk |
Composition | ✔ Yes (reverse declaration) | Destruction order matters |
Inheritance | ✔ Yes (derived → base) | Use virtual destructors in base |
Base pointer to derived object | ✔ Only base (❌) | Unless base destructor is virtual |
Base pointer + virtual destructor | ✔ Derived → Base (✔) | Correct cleanup in polymorphism |
Best Practices
- Always pair
new
withdelete
(or better, use smart pointers). - Make destructors virtual in base classes used polymorphically.
- Follow the Rule of Three/Five/Zero when managing resources.
- Use RAII to tie resource lifetime to object lifetime.