CLOSE
Updated on 17 Jun, 202523 mins read 26 views

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:

RuleDescription
NameSame as the class name, prefixed with a tilde ~
ParametersNone allowed (no overloading)
Return TypeNone (not even void)
QuantityOnly one destructor per class allowed

Automatic Invocation of Destructors

Destructors are automatically invoked:

  1. When a local object goes out of scope
  2. When an object is explicitly deleted via delete or delete[]

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:

  1. Reverse of construction (Last Constructed → First Destroyed)
  2. Derived to Base (In inheritance)
  3. 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 calledmemory 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

ScenarioDestructor Called?Notes
Local (stack) object✔ YesAutomatically at end of scope
Heap object (with delete)✔ YesMust manually delete
Heap object (no delete)❌ NoMemory 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 with delete (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.