LearnContact
Lesson 219 min read

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.

Object Created
Constructor Initializes State
Object Used
Lifetime Ends
Destructor Performs Cleanup

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.
The Main Idea

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.

Basic Destructor
class Student
{
public:
    ~Student()
    {
        // Cleanup code
    }
};
Destructor Concept
Object Exists
      │
      ▼
Lifetime Ends
      │
      ▼
Destructor Executes
      │
      ▼
Cleanup Completes
      │
      ▼
Object Destroyed
FeatureDestructor
PurposePerform cleanup during destruction
NameClass name prefixed with ~
Return TypeNone
ParametersNone
OverloadingNot allowed
InvocationOccurs as part of object destruction
Simple Definition

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.

Important Distinction

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.

Check In
Use Room
Begin Checkout
Return Resources
Stay Ends

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 AnalogyC++
Reservation beginsObject lifetime begins
Check-in setupConstructor
Hotel stayObject in use
Checkout procedureDestructor
Stay endsObject lifetime ends
Analogy
🏨 Check In
      │
      ▼
🛏️ Use Room
      │
      ▼
🧹 Checkout
      │
      ▼
🔑 Return Resources
      │
      ▼
🚪 Stay Ends

Characteristics 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.
Constructor and Destructor Names
class Student
{
public:
    Student()   // Constructor
    {
    }

    ~Student()  // Destructor
    {
    }
};
One 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.

General Syntax
class ClassName
{
public:
    ~ClassName()
    {
        // Cleanup code
    }
};
Student Destructor
class Student
{
public:
    ~Student()
    {
        // Cleanup code
    }
};
PartMeaning
~Identifies the function as a destructor
StudentMust match the class name
()Destructor accepts no parameters
{ }Destructor body
public:Allows normal destruction from accessible contexts
Incorrect and Correct
// ❌ Incorrect
void ~Student()
{
}

// ❌ Incorrect
~Student(int id)
{
}

// ✅ Correct
~Student()
{
}
Destructor Rules

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.

Local Object
void run()
{
    Student student1;

    // student1 exists here
}
// student1 is destroyed here
Enter Scope
Object Initialized
Object Used
Leave Scope
Destructor Invoked
Dynamic Object
Student* student1 = new Student;

delete student1;
// Destructor runs as part of delete
delete Is for Dynamic Objects

Use 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.

Example 1
#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.
Enter main()
Construct student1
Use student1
Leave main()
Destroy student1
Output
Constructor Called
Destructor Called
Automatic Lifetime Management

For 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.

Creation Order
Student student1;
Student student2;
Student student3;
Lifetime Order
Construction
────────────
1. student1
2. student2
3. student3

Destruction
───────────
1. student3
2. student2
3. student1

Construction Order

  • student1
  • student2
  • student3

Destruction Order

  • student3
  • student2
  • student1
Construct student1
Construct student2
Construct student3
Destroy student3
Destroy student2
Destroy student1
Reverse Construction Order

For automatic objects in the same scope, objects are destroyed in the reverse order of their completed construction.

LIFO Is Context-Dependent

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.

Example 2
#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.
Output
Constructing Student 1
Constructing Student 2
Destroying Student 2
Destroying Student 1
Visual Order
Create
──────
student1
   │
   ▼
student2

Destroy
───────
student2
   │
   ▼
student1
Use Identifiers While Learning

Printing 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.

RAII with a File Stream
#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.

Create LogFile
Open File Resource
Use Resource
Destroy LogFile
Destroy ofstream Member
Release File Resource
Modern C++ Approach

Prefer resource-managing types whose destructors already perform cleanup. This reduces the need to write manual cleanup code.

Manual Resource Ownership Example
class Number
{
private:
    int* value;

public:
    Number(int n)
        : value(new int(n))
    {
    }

    ~Number()
    {
        delete value;
    }
};
Manual Ownership Requires More Work

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.

FeatureConstructorDestructor
PurposeInitialize objectPerform destruction cleanup
When UsedBeginning of lifetimeEnd of lifetime
NameSame as classClass name prefixed with ~
Return TypeNoneNone
ParametersCan accept parametersCannot accept parameters
OverloadingAllowedNot allowed
Number Per ClassMultiple possibleOnly one
Typical ResponsibilityEstablish stateRelease 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.
Constructor
Object Lifetime
Destructor
Complete Lifetime

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.

Scope-Based Locking
{
    std::lock_guard<std::mutex> guard(mutex);

    // Protected work
}
// guard is destroyed here
// The mutex is unlocked automatically
Resource Lifetime
Acquire Resource
       │
       ▼
Object Owns Resource
       │
       ▼
Use Resource
       │
       ▼
Object Destroyed
       │
       ▼
Release Resource

Advantages 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.
RAII

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

Calling a Destructor Manually

Explicit destructor calls are rarely appropriate and can lead to lifetime errors if the object is later destroyed again.

Giving the Destructor Parameters

Destructors do not accept parameters.

Giving the Destructor a Return Type

Destructors have no return type, not even void.

Expecting Multiple Destructors

A class can have only one destructor.

Using delete on a Local Object

delete is only for suitable dynamically allocated objects created through matching new expressions.

Managing Raw Resources Unnecessarily

Prefer standard containers and smart pointers when they can own the resource safely.

Manual Destructor Call
Student student1;

// ❌ Usually incorrect
// student1.~Student();

// Let normal lifetime rules destroy it automatically.
Parameters Are Not Allowed
// ❌ Incorrect
// ~Student(int id)
// {
// }

// ✅ Correct
~Student()
{
}
No Return Type
// ❌ Incorrect
// void ~Student()
// {
// }

// ✅ Correct
~Student()
{
}
Do Not delete Local Objects
Student student1;

// ❌ Incorrect
// delete &student1;
Manual Destructor Calls

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.
Prefer Rule of Zero
#include <memory>

class Number
{
private:
    std::unique_ptr<int> value;

public:
    Number(int n)
        : value(std::make_unique<int>(n))
    {
    }

    // No custom destructor required
};
Virtual Destructor for a Polymorphic Base
class Base
{
public:
    virtual ~Base() = default;
};

class Derived : public Base
{
};
Prefer Rule of Zero

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.

Polymorphic Destruction

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.

Next Lesson →

Encapsulation