Destructors in C++

Do not miss this exclusive book on Binary Tree Problems. Get it now for free.

A destructor in C++ is a member function associated with each class and is called when the scope of the object of the classes is over to delete all data allocated for the object.

Its objective is to destroy all the objects that have been created by the constructor(s) of the class so that all the resources (such as opened files, opened sockets, database connections, resource locks, etc.) that had been acquired by an object of the class during its lifetime are freed.

Similar to a constructor, its name is the same as the class name but is preceded by a tilde sign (~). For example, the destructor for a class called vector would typically look like:

~ vector()
{
    statements;
    ...
}

Invocation of a Destructor:

A destructor is called automatically by the compiler when the object goes out of scope or upon exit from the program to clear the memory that is no longer accessible. However, when the pointer to an object goes out of scope, a destructor is not called implicitly.

It can also be invoked implicitly by using the delete operator or explicitly as follows:

vector v;
v.~vector();

In this example, an object called v of the vector class has been created and then destroyed by calling the destructor. After this line, if the object v is ever referred to in the program, it will show compiler error.

Characteristics of a Destructor:

  1. Similar to a constructor, the destructor must always be declared in the public section of the class. Failure to do so mostly results in a compiler error.
  2. A destructor never takes any arguments or returns any value.
  3. There can be only a single destructor for a class, that is destructors do not support function overloading.
  4. When the destructor function is not defined by the programmer, the compiler automatically generates a default destructor for the class.

If a class has a pointer to some memory allocated dynamically, there is a need to write a destructor function to release this memory before the class object is destroyed because the default destructor does not work in this case. It is important to do so to avoid a memory leak.
It is considered to be a good programming practice to define a destructor for a class to handle all such exceptions that cannot be handled by the default destructor that the compiler generates.

Let us understand the concept that we have learned so far by taking an illustration:

#include <iostream>
using namespace std;
int number = 0;
class vector{
    int x;
    int y;
    public:
    vector() //Constructor
    {
        number++;
        cout<<"Constructor called for object "<<number<<endl;
    }
    ~vector() //Destructor
    {
        cout<<"Destructor called for object "<<number<<endl;
        number--;
    }
};
int main(){
    vector v1,v2,v3;
    return 0;
}

The output for the above program is:

Constructor called for object 1
Constructor called for object 2
Constructor called for object 3
Destructor called for object 3
Destructor called for object 2
Destructor called for object 1

In the above example, a class called vector has been created. It consists of two member functions - its default constructor and destructor. Whenever these functions are called for any of the objects of this class, the corresponding action is displayed in the output.

The above example illustrates the fact that the objects are destroyed in the reverse order of their creation. This means that the object that is declared last, is destroyed first at the termination of the program.

Destructors in Inheritance:

A derived class does not inherit the destructor from its base class. However, the destructor of the derived class automatically invokes the destructor of the base class.
The order of execution of the destructors follows the bottom-up approach. The destructor of the base class gets called after the destructor of the derived class. Hence, the order of destructor invocation is the reverse of the order of constructor invocation.

Let us understand this with the help of a program:

#include <iostream>
using namespace std;

class base{
    public:
    base(){
        cout<<"Base class constructor"<<endl;
    }
    ~base(){
        cout<<"Base class desctructor"<<endl;
    }
};
class derived : public base{
    public: 
    derived(){ 
    cout<<"Derived class constructor"<<endl; 
    }
    ~derived(){ 
        cout<<"Derived class destructor"<<endl; 
    }
};

int main()
{
    derived obj;
/*automatically executes both base and derived class constructors and destructors because of inheritance*/
    return 0;
}

The output for the above program is as follows:

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

In the above example, two classes - the base and the derived class have been declared. The statement "derived obj;" is used to call the constructors and destructors of both the base and the derived class. The significance of this program lies in the order of the invocation of these member functions.

Virtual Destructors:

In case of a virtual function, the C++ compiler determines which function to use at run time based on the type of the object pointed to by the base pointer, rather than the type of the pointer.
There cannot be virtual constructors, but destructors can be declared virtual for a class. In C++, the constructor cannot be virtual because at the time a constructor of a class is invoked, there is no virtual table in the memory, hence no virtual pointer is defined till then.
On the contrary, it is important to declare base destructors as virtual to prevent memory leaks and to manage the process structure.

Sometimes when an object of a derived class is deleted by using a pointer to its base class, there are some errors or unusual behavior. This problem can be solved by declaring the destructor of the base class as virtual.
By declaring the base class destructor as virtual, there is assurance that the object of the derived class will always be destructed properly and destructors of both the base class and derived class will be invoked.

For example,

// A program with virtual destructor 
#include<iostream> 
using namespace std; 
class base { 
    public: 
	base()	 
        { cout<<"Base constructor"<<endl; } 
	virtual ~base() 
        { cout<<"Base destructor"<<endl; }	 
}; 
class derived: public base { 
    public: 
	derived()	 
        { cout<<"Derived constructor"<<endl; } 
	~derived() 
        { cout<<"Derived destructor"<<endl; } 
}; 
int main() { 
    derived *obj = new derived(); 
    base *obj2 = obj; 
    delete obj2; 
    return 0; 
} 

The output for the above program is as follows:

Base constructor
Derived constructor
Derived destructor
Base destructor

In this example, the pointers of the type base and derived, obj2 and obj are respectively used to invoke the member functions of the two classes.

This article is a sequel to the article called Constructors in C++.
This brings us to the end of the article at OpenGenus in which we have learned about Destructors in C++!

Sign up for FREE 3 months of Amazon Music. YOU MUST NOT MISS.