In C++, a destructor is a special member function that is called automatically when an object goes out of scope or is explicitly deleted.
The primary role of destructors is to free up resources (like memory) that were allocated during the lifetime of the object.
Destructors have the following characteristics:
Key Characteristics of Destructors
- Same name as the class, preceded by a tilde ~.
- No parameters and no return type, not even void.
- Only one destructor per class (no overloading).
- Called automatically when an object is destroyed, either when it goes out of scope or when delete is used on a dynamically allocated object.
1. Basic Destructor Example
A simple example to demonstrate when a destructor is called.
#include <iostream> using namespace std; class Car { public: // Constructor Car() { cout << "Constructor called!" << endl; } // Destructor ~Car() { cout << "Destructor called!" << endl; } }; int main() { Car car1; // Constructor is called cout << "Main function" << endl; return 0; // Destructor is called as car1 goes out of scope }
Explanation:
- When Car car1; is declared, the constructor is called.
- When main finishes, the destructor is called automatically as car1 goes out of scope.
Output:
Constructor called! Main function Destructor called!
2. Destructor and Dynamic Memory Allocation
Destructors are essential when using dynamic memory allocation to ensure that memory is freed when the object is destroyed.
#include <iostream> using namespace std; class Array { private: int* data; int size; public: // Constructor that allocates memory Array(int s) { size = s; data = new int[size]; cout << "Array of size " << size << " created." << endl; } // Destructor to release allocated memory ~Array() { delete[] data; cout << "Array memory freed." << endl; } }; int main() { Array arr(5); // Constructor allocates memory for array of size 5 // Array destructor is called here, releasing the allocated memory return 0; }
Explanation:
- The constructor allocates memory using new, and the destructor releases it using delete[].
- When arr goes out of scope at the end of main, the destructor is called, freeing the allocated memory.
Output:
Array of size 5 created. Array memory freed.
3. Destructor for Classes with Multiple Objects
Destructors are called for each object individually when multiple objects of the same class go out of scope.
#include <iostream> using namespace std; class Car { public: Car() { cout << "Constructor called!" << endl; } ~Car() { cout << "Destructor called!" << endl; } }; int main() { Car car1; Car car2; cout << "Main function ends" << endl; return 0; }
Explanation:
- The destructors are called for car1 and car2 in the reverse order of their creation, following the last in, first out (LIFO) rule.
Output:
Constructor called! Constructor called! Main function ends Destructor called! Destructor called!
4. Destructor in a Class with a Pointer Member
If a class has a pointer member, the destructor should ensure that any dynamically allocated memory is freed when the object is destroyed.
#include <iostream> using namespace std; class Person { private: string* name; public: // Constructor Person(const string& n) { name = new string(n); cout << "Constructor called for " << *name << endl; } // Destructor ~Person() { cout << "Destructor called for " << *name << endl; delete name; // Free dynamically allocated memory } }; int main() { Person person1("Alice"); Person person2("Bob"); cout << "End of main function" << endl; return 0; }
Explanation:
- name is dynamically allocated in the constructor.
- The destructor deletes name to free memory.
Output:
Constructor called for Alice Constructor called for Bob End of main function Destructor called for Bob Destructor called for Alice
5. Destructors in Classes with Inheritance
In inheritance, destructors are called in the reverse order of constructor calls, starting from the most derived class back up the inheritance chain.
#include <iostream> using namespace std; class Base { public: Base() { cout << "Base class constructor called!" << endl; } virtual ~Base() { cout << "Base class destructor called!" << endl; } }; class Derived : public Base { public: Derived() { cout << "Derived class constructor called!" << endl; } ~Derived() { cout << "Derived class destructor called!" << endl; } }; int main() { Derived d; // Calls Base and Derived constructors return 0; // Calls Derived and Base destructors }
Explanation:
- Derived constructor calls Base constructor first.
- Destructors are called in reverse order: first Derived, then Base.
Output:
Base class constructor called! Derived class constructor called! Derived class destructor called! Base class destructor called!
6. Destructor with Polymorphism and Virtual Destructors
When using polymorphism, if a base class pointer points to a derived class object, the destructor should be virtual to ensure proper cleanup.
#include <iostream> using namespace std; class Base { public: Base() { cout << "Base constructor called!" << endl; } virtual ~Base() { // Virtual destructor cout << "Base destructor called!" << endl; } }; class Derived : public Base { public: Derived() { cout << "Derived constructor called!" << endl; } ~Derived() { cout << "Derived destructor called!" << endl; } }; int main() { Base* ptr = new Derived(); // Base pointer to Derived object delete ptr; // Correctly calls both Derived and Base destructors return 0; }
Explanation:
- Declaring the base class destructor as virtual ensures that both Derived and Base destructors are called when delete ptr is used.
- Without virtual, only Base destructor would be called, causing a potential memory leak.
Output:
Base constructor called! Derived constructor called! Derived destructor called! Base destructor called!
7. Destructor with Static Data Members
Static members are shared by all objects of a class and are not destroyed with each object. Thus, destructors are not responsible for cleaning up static members.
#include <iostream> using namespace std; class Counter { public: static int count; Counter() { count++; cout << "Constructor called. Count = " << count << endl; } ~Counter() { count--; cout << "Destructor called. Count = " << count << endl; } }; int Counter::count = 0; // Initialize static member int main() { Counter c1; Counter c2; { Counter c3; } // c3 goes out of scope, so its destructor is called here cout << "End of main function" << endl; return 0; }
Explanation:
- count is a static member, shared among all objects.
- Each time an object is created, count is incremented in the constructor and decremented in the destructor.
Output:
Constructor called. Count = 1 Constructor called. Count = 2 Constructor called. Count = 3 Destructor called. Count = 2 End of main function Destructor called. Count = 1 Destructor called. Count = 0
8. Destructor in Classes with File Handling
When dealing with files, a destructor can be used to close an open file to ensure no data is lost.
#include <iostream> #include <fstream> using namespace std; class FileWriter { private: ofstream file; public: // Constructor opens file FileWriter(const string& filename) { file.open(filename); if (file.is_open()) { cout << "File opened successfully." << endl; } } // Destructor closes file ~FileWriter() { if (file.is_open()) { file.close(); cout << "File closed successfully." << endl; } } void write(const string& data) { if (file.is_open()) { file << data << endl; } } }; int main() { FileWriter writer("example.txt"); writer.write("Hello, World!"); return 0; // FileWriter destructor closes the file }
Explanation:
- The constructor opens a file, and the destructor closes it, ensuring data is saved and resources are freed.
Output:
File opened successfully. File closed successfully.
Summary Table of Destructor Use Cases
Use Case | Description |
---|---|
Basic Destructor | Automatically called when an object goes out of scope |
Dynamic Memory Allocation | Frees dynamically allocated memory |
Multiple Objects | Each object has its destructor called individually |
Pointer Members | Frees dynamically allocated pointer members |
Inheritance | Destructors called in reverse order of inheritance hierarchy |
Virtual Destructors | Ensures proper cleanup in polymorphic classes |
Static Members | Not affected by destructors, shared by all objects |
File Handling | Closes files or releases resources when an object goes out of scope |
Complete Example
This example demonstrates a class using destructors to manage dynamically allocated memory and file handling.
#include <iostream> #include <fstream> using namespace std; class ResourceManager { private: int* data; ofstream file; public: ResourceManager(const string& filename, int size) { // Allocate memory data = new int[size]; for (int i = 0; i < size; i++) { data[i] = i + 1; } cout << "Memory allocated." << endl; // Open file file.open(filename); if (file.is_open()) { cout << "File opened." << endl; for (int i = 0; i < size; i++) { file << data[i] << " "; } file << endl; } } // Destructor to release resources ~ResourceManager() { delete[] data; cout << "Memory deallocated." << endl; if (file.is_open()) { file.close(); cout << "File closed." << endl; } } }; int main() { ResourceManager manager("data.txt", 5); return 0; }
Explanation:
- The constructor allocates memory and opens a file.
- The destructor deallocates memory and closes the file to ensure all resources are properly managed.
Sample Output:
Memory allocated. File opened. Memory deallocated. File closed.
Destructors in C++ play a crucial role in resource management, especially with dynamically allocated memory, file handling, and complex class hierarchies.