Lesson 48 of 60 – Destructors in C++
80%

Destructors in C++

A destructor is a special member function of a class that is automatically called when an object is destroyed. Destructors are mainly used to perform cleanup work, such as releasing resources owned by an object.

Note: A destructor has the same name as the class, preceded by the ~ symbol, and it does not have a return type or parameters.

1. What is a Destructor?

A destructor is a special member function that is automatically called when an object is destroyed.

class Student {

public:

    ~Student() {

        std::cout << "Destructor called";
    }
};

The destructor is used when the lifetime of a Student object ends.

2. Destructor Syntax

The basic syntax of a destructor is:

class ClassName {

public:

    ~ClassName() {

        // cleanup code

    }
};

The destructor name must match the class name and begin with ~.

3. Destructor Has No Return Type

A destructor does not have a return type.

class Demo {

public:

    ~Demo() {

        std::cout << "Destructor";
    }
};

You must not write void or any other return type before a destructor.

4. Destructor Has No Parameters

A destructor cannot take parameters.

class Demo {

public:

    ~Demo() {

        std::cout << "Object destroyed";
    }
};

There can be only one destructor for a class.

5. When is a Destructor Called?

A destructor is called automatically when an object's lifetime ends. For a local object, this normally happens when the object goes out of scope.

class Demo {

public:

    ~Demo() {

        std::cout << "Destructor called";
    }
};

int main() {

    Demo object;

    return 0;
}

When main() ends, the local object is destroyed and its destructor is called.

6. Destructor and Scope

A local object's destructor is called when the object leaves its scope.

class Demo {

public:

    ~Demo() {

        std::cout << "Destroyed"
                  << std::endl;
    }
};

int main() {

    {
        Demo object;
    }

    std::cout << "After block";

    return 0;
}

The destructor runs when the closing brace of the inner block is reached.

7. Simple Destructor Example

#include <iostream>

class Demo {

public:

    Demo() {

        std::cout << "Constructor called"
                  << std::endl;
    }

    ~Demo() {

        std::cout << "Destructor called"
                  << std::endl;
    }
};

int main() {

    Demo object;

    std::cout << "Inside main"
              << std::endl;

    return 0;
}

The constructor runs when the object is created and the destructor runs when its lifetime ends.

8. Constructor vs Destructor

Constructor Destructor
Initializes an object. Performs cleanup when an object is destroyed.
Has the same name as the class. Has the class name preceded by ~.
Can have parameters. Cannot have parameters.
Can be overloaded. Cannot be overloaded.
Called during object initialization. Called when object lifetime ends.

9. Destructor for Cleanup

Destructors can be used to release resources owned by an object.

class Resource {

public:

    Resource() {

        std::cout << "Resource acquired";
    }

    ~Resource() {

        std::cout << "Resource released";
    }
};

The destructor provides a place for cleanup associated with the object's lifetime.

10. Destructor with Dynamic Memory

If a class directly owns memory allocated with new, its destructor can release that memory.

class Array {

private:

    int* data;

public:

    Array() {

        data = new int[5];
    }

    ~Array() {

        delete[] data;
    }
};

Here, delete[] releases the dynamically allocated array.

11. Destructor and delete

When an object created using new is destroyed using delete, its destructor is called before the object's storage is released.

class Student {

public:

    ~Student() {

        std::cout << "Destructor called";
    }
};

int main() {

    Student* student =
        new Student();

    delete student;

    return 0;
}

12. Destructor with delete[]

When dynamically allocated arrays are released with delete[], destructors for the array elements are invoked.

class Student {

public:

    ~Student() {

        std::cout << "Destroyed"
                  << std::endl;
    }
};

int main() {

    Student* students =
        new Student[3];

    delete[] students;

    return 0;
}

13. Order of Destruction

For local objects in the same scope, destruction generally happens in the reverse order of construction.

class Demo {

private:

    int id;

public:

    Demo(int value)
        : id(value) {
    }

    ~Demo() {

        std::cout << id
                  << std::endl;
    }
};

int main() {

    Demo first(1);
    Demo second(2);
    Demo third(3);

    return 0;
}

The destructors run in the order:

3
2
1

14. Destructor and Nested Scope

class Demo {

private:

    int id;

public:

    Demo(int value)
        : id(value) {
    }

    ~Demo() {

        std::cout <<
            "Destroyed: "
            << id
            << std::endl;
    }
};

int main() {

    Demo first(1);

    {
        Demo second(2);

        std::cout <<
            "Inside block"
            << std::endl;
    }

    std::cout <<
        "Outside block";

    return 0;
}

The destructor for second runs when the inner block ends.

15. Destructor Cannot Be Overloaded

A class can have multiple constructors, but it can have only one destructor.

class Demo {

public:

    ~Demo() {

        std::cout << "Destroyed";
    }
};

You cannot define another destructor with different parameters because destructors cannot have parameters.

16. Virtual Destructor

A base class destructor is often declared virtual when objects of derived classes may be deleted through a base-class pointer.

class Base {

public:

    virtual ~Base() {

        std::cout <<
            "Base destructor";
    }
};

A virtual destructor helps ensure that the appropriate derived destructor is called when deleting through a base pointer.

17. Base and Derived Destructor Example

class Base {

public:

    virtual ~Base() {

        std::cout <<
            "Base destroyed"
            << std::endl;
    }
};

class Derived : public Base {

public:

    ~Derived() {

        std::cout <<
            "Derived destroyed"
            << std::endl;
    }
};

int main() {

    Base* object =
        new Derived();

    delete object;

    return 0;
}

With a virtual base destructor, deleting through the base pointer allows the derived destructor to run correctly before the base destructor.

18. Destructor and Inheritance

When a derived object is destroyed, the derived destructor runs before the base destructor.

class Base {

public:

    virtual ~Base() {

        std::cout <<
            "Base"
            << std::endl;
    }
};

class Derived : public Base {

public:

    ~Derived() {

        std::cout <<
            "Derived"
            << std::endl;
    }
};

For a derived object, destruction proceeds from the most-derived part toward the base part.

19. Destructor and Member Objects

When an object is destroyed, its member objects are also destroyed according to C++ object lifetime rules.

class Engine {

public:

    ~Engine() {

        std::cout <<
            "Engine destroyed"
            << std::endl;
    }
};

class Car {

private:

    Engine engine;

public:

    ~Car() {

        std::cout <<
            "Car destructor"
            << std::endl;
    }
};

Member objects are destroyed after the containing object's destructor body finishes.

20. Destructor and File Resources

A class that owns a resource such as a file handle can use its destructor as part of cleanup.

class FileManager {

public:

    FileManager() {

        std::cout <<
            "File opened"
            << std::endl;
    }

    ~FileManager() {

        std::cout <<
            "File closed"
            << std::endl;
    }
};

In real programs, standard library resource-management types are often preferred because they automatically manage resources safely.

21. Destructor and Exception Safety

Destructors are an important part of C++ resource management because local objects are destroyed automatically when their scope ends, even when control leaves the scope because of an exception.

class Resource {

public:

    ~Resource() {

        std::cout <<
            "Cleanup";
    }
};

void process() {

    Resource resource;

    // Work happens here.
}

This behavior is one of the foundations of RAII in C++.

22. Destructor and RAII

RAII means Resource Acquisition Is Initialization. In C++, a resource can be associated with an object's lifetime so that its destructor performs cleanup automatically.

class ResourceManager {

public:

    ResourceManager() {

        // Acquire resource
    }

    ~ResourceManager() {

        // Release resource
    }
};

RAII is a major C++ technique for safe resource management.

23. Destructor with a Class Example

#include <iostream>

class Student {

private:

    int id;

public:

    Student(int studentId)
        : id(studentId) {

        std::cout <<
            "Student created: "
            << id
            << std::endl;
    }

    ~Student() {

        std::cout <<
            "Student destroyed: "
            << id
            << std::endl;
    }
};

int main() {

    Student student(101);

    std::cout <<
        "Student is active"
        << std::endl;

    return 0;
}

24. Destructor with Dynamic Array

#include <iostream>

class Numbers {

private:

    int* data;

public:

    Numbers(int size) {

        data = new int[size];

        std::cout <<
            "Array allocated"
            << std::endl;
    }

    ~Numbers() {

        delete[] data;

        std::cout <<
            "Array released"
            << std::endl;
    }
};

int main() {

    Numbers numbers(10);

    return 0;
}
Important: In modern C++, prefer containers such as std::vector or other RAII-based types instead of manually managing dynamic arrays whenever possible.

25. Practical Resource Management Example

#include <iostream>

class DatabaseConnection {

public:

    DatabaseConnection() {

        std::cout <<
            "Database connected"
            << std::endl;
    }

    ~DatabaseConnection() {

        std::cout <<
            "Database connection closed"
            << std::endl;
    }
};

void processData() {

    DatabaseConnection connection;

    std::cout <<
        "Processing data"
        << std::endl;
}

int main() {

    processData();

    std::cout <<
        "Program continues";

    return 0;
}

When processData() finishes, the local connection object is destroyed and its destructor runs.

26. Common Destructor Mistakes

  • Giving a destructor a return type.
  • Adding parameters to a destructor.
  • Using a destructor name different from the class name.
  • Trying to overload a destructor.
  • Using delete instead of delete[] for an array allocated with new[].
  • Releasing the same resource more than once.
  • Forgetting a virtual destructor in a polymorphic base class when deletion through a base pointer is intended.
  • Manually managing resources when a standard RAII type would be safer.

27. Destructor vs delete

Destructor delete
A special member function of a class. An operator used to destroy an object created with new.
Runs as part of object destruction. Causes destruction of a dynamically allocated object and releases its storage.
Written using ~ClassName(). Written as delete pointer.
Cannot be called with parameters. Used with a pointer to dynamically allocated storage.

28. Best Practices for Destructors

  • Use destructors to release resources owned by an object.
  • Keep destructors simple and focused on cleanup.
  • Do not throw exceptions from destructors during normal destruction.
  • Use RAII and standard library resource-management types whenever possible.
  • Use a virtual destructor in a polymorphic base class when objects may be deleted through base pointers.
  • Match delete with new and delete[] with new[] when manual memory management is used.
  • Avoid unnecessary manual resource management.

29. Real-World Uses of Destructors

Destructors are especially important for classes that manage resources.

  • Memory: Releasing manually allocated memory.
  • Files: Closing file resources.
  • Database Connections: Releasing connection resources.
  • Network Resources: Cleaning up owned communication resources.
  • Locks: Releasing synchronization resources.
  • Custom Resources: Cleaning up resources controlled by a class.

30. Destructors – Final Summary

Concept Meaning
Destructor A special member function called when an object is destroyed.
Syntax ~ClassName()
Return Type A destructor has no return type.
Parameters A destructor cannot have parameters.
Overloading A class cannot have multiple destructors.
Cleanup Destructors can release resources owned by an object.
Virtual Destructor Useful in polymorphic base classes for correct destruction through base pointers.
RAII Associates resource management with object lifetime.
class Student {

public:

    Student() {

        std::cout <<
            "Object created"
            << std::endl;
    }

    ~Student() {

        std::cout <<
            "Object destroyed"
            << std::endl;
    }
};

int main() {

    Student student;

    return 0;
}

📌 Key Points

  • A destructor is automatically called when an object's lifetime ends.
  • A destructor has the same name as the class with a ~ prefix.
  • A destructor has no return type.
  • A destructor cannot have parameters and cannot be overloaded.
  • Destructors are commonly used for cleanup and resource management.
  • Objects created with new are destroyed using delete, which invokes their destructor.
  • Arrays created with new[] should be released with delete[].
  • A polymorphic base class generally needs a virtual destructor when deletion through a base pointer is intended.
  • RAII connects resource management with object lifetime.
  • Modern C++ generally prefers standard RAII-based resource managers over unnecessary manual resource management.

🧠 Quick Quiz

Question: Which statement about a C++ destructor is correct?