C++ Destructors


Introduction

A destructor in C++ is a special member function of a class that is executed automatically when an object of the class is destroyed. The primary purpose of a destructor is to release any resources that were allocated to the object during its lifetime. This could include freeing dynamically allocated memory or closing files or network connections.

Just like constructors, destructors have the same name as the class, but they are preceded by a tilde (~). Unlike constructors, destructors do not take any parameters and do not return any value.


1. What is a Destructor?

A destructor is called when an object goes out of scope or is explicitly deleted. The destructor is responsible for performing the necessary cleanup, such as freeing memory or closing files, to prevent resource leaks. It is invoked automatically when the object is destroyed, and you cannot call it explicitly.

Key Points:

  • A destructor has the same name as the class but with a tilde (~) before the name.
  • It does not take any parameters and does not return any value.
  • Each class can only have one destructor.
  • Destructors are automatically called when an object goes out of scope (in local variables) or when the delete operator is used for dynamically allocated memory.

2. Syntax of a Destructor

The syntax for a destructor in C++ is straightforward. It follows the same name as the class, with a tilde (~) in front of the class name.

Syntax:

class ClassName {
public:
    // Destructor
    ~ClassName() {
        // Cleanup code
    }
};

3. How Does a Destructor Work?

A destructor is called automatically when:

  • An object goes out of scope (in case of local objects).
  • The delete operator is used for dynamically allocated memory (in case of objects created with new).

Once the destructor is called, the object is destroyed, and its memory is reclaimed.

Example 1: Destructor in Action

#include <iostream>
using namespace std;

class Person {
public:
    string name;

    // Constructor to initialize the object
    Person(string n) {
        name = n;
        cout << "Constructor called for " << name << endl;
    }

    // Destructor to clean up resources
    ~Person() {
        cout << "Destructor called for " << name << endl;
    }
};

int main() {
    // Creating a Person object
    Person person1("Alice");

    {
        // Creating another Person object inside a block
        Person person2("Bob");
    }  // person2 goes out of scope here, calling the destructor

    return 0;
}

Output:

Constructor called for Alice
Constructor called for Bob
Destructor called for Bob
Destructor called for Alice

Explanation:

  • person1 and person2 are created. When person2 goes out of scope, its destructor is called.
  • When the program ends and person1 goes out of scope, its destructor is also called.

4. Destructor for Dynamically Allocated Memory

When an object is created using the new keyword (dynamically allocated), the destructor is called when the object is explicitly deleted using the delete operator.

Example 2: Destructor with Dynamic Memory Allocation

#include <iostream>
using namespace std;

class Person {
public:
    string *name;

    // Constructor
    Person(string n) {
        name = new string(n);  // Dynamically allocated memory
        cout << "Constructor called: " << *name << endl;
    }

    // Destructor to clean up dynamically allocated memory
    ~Person() {
        delete name;  // Deallocating memory
        cout << "Destructor called and memory freed." << endl;
    }
};

int main() {
    // Dynamically allocating memory for a Person object
    Person *person = new Person("Charlie");

    // Deleting the object to call the destructor
    delete person;

    return 0;
}

Output:

Constructor called: Charlie
Destructor called and memory freed.

Explanation:

  • The Person object is dynamically created using new. This means memory is allocated for the object and for its name string.
  • The destructor is responsible for releasing the memory allocated to name using delete.
  • The delete person; line ensures that the destructor is called, and the memory is properly freed.

5. When Are Destructors Called?

Destructors are invoked in the following scenarios:

  1. When an object goes out of scope:

    • For local objects, the destructor is automatically called when the object goes out of scope (e.g., when a function ends or a block of code finishes execution).
  2. When an object is deleted:

    • For objects created dynamically using the new operator, the destructor is called when delete is used.
  3. At the end of a program:

    • For static objects (global objects or objects declared as static), destructors are called when the program ends.

6. Destructor and Inheritance

In case of inheritance, if a class is inherited from a base class, the base class destructor is automatically called before the derived class destructor when an object is destroyed. If the derived class has its own destructor, it will be called first.

If a derived class has a destructor, it is important to make the base class destructor virtual to ensure the correct destructor is called when a base class pointer is used to delete an object.

Example 3: Destructor in Inheritance

#include <iostream>
using namespace std;

class Base {
public:
    // Base class constructor
    Base() {
        cout << "Base class constructor" << endl;
    }

    // Base class destructor
    virtual ~Base() {
        cout << "Base class destructor" << endl;
    }
};

class Derived : public Base {
public:
    // Derived class constructor
    Derived() {
        cout << "Derived class constructor" << endl;
    }

    // Derived class destructor
    ~Derived() {
        cout << "Derived class destructor" << endl;
    }
};

int main() {
    // Creating an object of the derived class
    Base *obj = new Derived();

    // Deleting the object
    delete obj;

    return 0;
}

Output:

Base class constructor
Derived class constructor
Derived class destructor
Base class destructor

Explanation:

  • The destructor of the derived class is called first, followed by the base class destructor. This is ensured by making the base class destructor virtual.
  • If virtual was omitted from the base class destructor, only the base class destructor would have been called, leading to potential resource leaks.

7. Important Considerations for Destructors

  • No parameters or return type: Destructors do not accept parameters and do not return any value.
  • No overloading: Unlike constructors, destructors cannot be overloaded. A class can only have one destructor.
  • Virtual destructors: In case of inheritance, destructors in base classes should be declared virtual to ensure the correct destructor is called.
  • No delete in destructors for local objects: Local objects that go out of scope do not require manual memory deallocation (it happens automatically).