Destructors in C++



A destructor is a special member function of a class that is executed automatically whenever an object of its class goes out of scope or whenever the delete expression is applied to a pointer to the object of that class.

Note: In C++, the static keyword has a different meaning. In this chapter, we're using static memory allocation for stack-based / non-dynamic memory allocation.

The syntax of defining a destructor manually is given below −

 ~class_name() { // body } 

Here's the syntax to define a destructor outside the class, but first we need to declare the destructor in the class −

 class_name { public: // Destructor Declaration ~class_name(); } // Defining Destructor class_name :: ~class_name() { // Body } 

Defining a Destructor Inside a Class

In this example, we have used the above syntax for defining a destructor manually inside a class. Here, destructor is not needed, we have used destructor just to illustrate how a destructor is defined inside a class −

 #include <iostream> using namespace std; class Example{ public: // Constructor called Example(void) { cout << "Constructor is called" << endl; } // Defining destructor inside class ~Example(void) { cout << "Destructor is called" << endl; } }; int main(){ Example ex; return 0; } 

The output of the above code is given below −

 Constructor is called Destructor is called 

Defining a Destructor Outside a Class

The example below demonstrates how to define a destructor(Example) outside the class using scope resolution operator. But first, it needs to be declared inside the class.

 #include <iostream> using namespace std; class Example { public: // Constructor called Example(void) { cout << "Constructor is called" << endl; } // Declaring destructor inside class ~Example(); }; // Defining destructor outside class using scope resolution operator Example :: ~Example(void) { cout << "Destructor is called" << endl; } int main(){ Example ex; return 0; } 

The output of the above code is given below −

 Constructor is called Destructor is called 

Why Do We Need Custom Destructors?

C++ provides a default destructor for every class as the classes are created. The default destructor cleans up the statically allocated memory. But, if we have used a pointer for dynamic memory allocation using new, then we need to define a custom destructor with delete operator to clean up the memory.

In this example, the destructor Example1 is just used for displaying the message and not to free the memory as the memory of class Example1 gets freed automatically. But the destructor Example2 is used for freeing the memory using delete operator.

 #include <iostream> using namespace std; class Example1 { public: // Constructor called Example1() { cout << "Constructor of Example1 is called" << endl; } // Destructor called ~Example1() { cout << "Destructor of Example1 is called" << endl; } }; class Example2 { private: int *ptr; public: Example2() { ptr = new int; // dynamically allocated memory *ptr = 42; cout << "Constructor of Example2 is called with ptr Value = " << *ptr << endl; } // Destructor called to free dynamically allocated memory ~Example2() { delete ptr; // dynamically allocated memory is freed cout << "Destructor of Example2 is called" << endl; } }; int main(){ Example1 ex1; // object with static memory allocation Example2 ex2; // object with dynamic memory allocation return 0; } 

The output of the above code is given below −

 Constructor of Example1 is called Constructor of Example2 is called with ptr Value = 42 Destructor of Example2 is called Destructor of Example1 is called 

Properties of Destructors in C++

Destructors in C++ have the following properties −

  • Destructors are automatically called when an object goes out of scope.
  • Destructor has the same name as the class and is denoted using tilde(~) sign.
  • It is automatically created when a class is created.
  • Destructors do not have any return type and do not accept any parameters/arguments.
  • You can not define more than one destructor.
  • Destructors cannot be inherited.
  • Destructors cannot be overloaded.

Automatic Destructor Call for Statically Allocated Objects

For objects that are created using static memory allocation, destructors are automatically called. Below is an example −

 #include <iostream> using namespace std; class MyClass { public: MyClass() { cout << "Constructor called" << endl; } ~MyClass() { cout << "Destructor called automatically" << endl; } }; int main() { cout << "Creating an object using static memory allocation" << endl; { MyClass obj; cout << "Object created" << endl; } // Object goes out of scope here, //destructor called automatically cout << "Object destroyed" << endl; return 0; } 

The output of the above code is given below −

 Creating an object using static memory allocation Constructor called Object created Destructor called automatically Object destroyed 

C++ Destructor for Dynamic Objects

For objects that are created using dynamic memory allocation, you need to manually call the destructor using delete. In the example below, we have manually deleted the object using the delete operator:

 #include <iostream> using namespace std; class MyClass { public: MyClass() { cout << "Constructor called" << endl; } ~MyClass() { cout << "Destructor called" << endl; } }; int main() { cout << "Creating an object using dynamic memory allocation:" << endl; // Dynamically creating an object MyClass *obj = new MyClass(); cout << "Object created" << endl; // Manually invoking destructor delete obj; cout << "Object destroyed manually using delete" << endl; return 0; } 

The output of the above code is given below −

 Creating an object using dynamic memory allocation: Constructor called Object created Destructor called Object destroyed manually using delete 

Destructor Call Order for Multiple Objects

Destructors are called in reverse order of construction for multiple objects. In the example below, we can see that the destructors are called in the reverse order −

 #include <iostream> using namespace std; class MyClass { private: int id; public: MyClass(int num) : id(num) { cout << "Constructor called for object " << id << endl; } ~MyClass() { cout << "Destructor called for object " << id << endl; } }; int main() {	cout << "Creating 3 objects:" << endl;	{	MyClass obj1(1); // First object	MyClass obj2(2); // Second object	MyClass obj3(3); // Third object	cout << "All objects created" << endl;	} // All objects go out of scope here	cout << "All objects destroyed" << endl;	return 0; } 

The output of the above code is given below −

 Creating 3 objects: Constructor called for object 1 Constructor called for object 2 Constructor called for object 3 All objects created Destructor called for object 3 Destructor called for object 2 Destructor called for object 1 All objects destroyed 

Destructors with Arrays

To clean the memory after an array gets deleted or gets out of scope, we need to call destructor for each array item to avoid any memory leak. There can be three different types of scenarios while using a destructor with an array.

  • Using Destructor with Static Array
  • Using Destructor with a Dynamic Array
  • Using Destructor with an Array of Pointers to Objects

Using a Destructor with a Static Array

In this example, we have a static array of objects, and the memory is freed automatically by the compiler. Here, the destructor is used just for displaying the message.

 #include <iostream> using namespace std; class Example { private: int id; public: Example(int i) : id(i) { cout << "Constructor called for object " << id << endl; } ~Example() { cout << "Destructor called for object " << id << endl; } }; int main(){ cout << "Creating array of 3 objects:" << endl; Example arr[3] = {Example(1), Example(2), Example(3)}; cout << "Array created" << endl; return 0; // Destructors called automatically } 

The output of the above code is given below −

 Creating array of 3 objects: Constructor called for object 1 Constructor called for object 2 Constructor called for object 3 Array created Destructor called for object 3 Destructor called for object 2 Destructor called for object 1 

Using a Destructor with a Dynamic Array

In this example, a dynamic array of objects is created using new[] operator. Here, the destructor is used with delete[] operator to clear the memory.

 #include <iostream> using namespace std; class Student { private: int rollNo; public: Student(int r = 0) // constructor with default argument { rollNo = r; cout << "Constructor called for roll no: " << rollNo << endl; } ~Student() { cout << "Destructor called for roll no: " << rollNo << endl; } }; int main() { cout << "Creating dynamic array of 3 students:" << endl; Student *students = new Student[3] {101, 102, 103}; // initialize with roll numbers cout << "\nDeleting array:" << endl; delete[] students; // destructor for all 3 objects return 0; } 

The output of the above code is given below −

 Creating dynamic array of 3 students: Constructor called for roll no: 101 Constructor called for roll no: 102 Constructor called for roll no: 103 Deleting array: Destructor called for roll no: 103 Destructor called for roll no: 102 Destructor called for roll no: 101 

Using a Destructor with an Array of Pointers to Objects

In this example, an array of pointers to objects is created. Here, the destructor is used with the delete operator to free the memory of each object individually as each pointer is pointing to a uniquely allocated object.

 #include <iostream> using namespace std; class Book { private: string title; public: Book(string t) : title(t) { cout << "Book created: " << title << endl; } ~Book() { cout << "Book destroyed: " << title << endl; } }; int main(){ cout << "Creating array of 3 books:" << endl; Book *library[3]; // Creating individual book objects library[0] = new Book("C++ Basics"); library[1] = new Book("CSS Basics"); library[2] = new Book("Javascript Basics"); cout << "\nDeleting books:" << endl; // Deleting each object individually for (int i = 0; i < 3; i++){ delete library[i]; } return 0; } 

The output of the above code is given below −

 Creating array of 3 books: Book created: C++ Basics Book created: CSS Basics Book created: Javascript Basics Deleting books: Book destroyed: C++ Basics Book destroyed: CSS Basics Book destroyed: Javascript Basics 

Common Mistakes While Working with Destructors

Some of the common beginner mistakes while working with destructors are listed below −

  • Using destructor for statically allocated memory. It gets deleted automatically, so there is no need to use a destructor.
  • For dynamically allocated memory, forgetting to use the delete.
  • In inheritance, not declaring the destructor as virtual in the base class.
  • For arrays, use delete[] instead of delete.
  • Trying to delete an already-deleted memory twice, also known as double deletion. Set the pointer to nullptr after deleting it, and check the pointer for nullptr before deletion.

Conclusion

Destructors are special member functions to clean up memory when objects go out of scope or program is executed. In C++, there is a default destructor for statically allocated memory, and custom destructors are used for clearing the dynamically allocated memory using the new operator.

Advertisements