C++ Destructors
Special methods for object cleanup
🧹 What are Destructors?
Destructors are special methods automatically called when objects are destroyed. They clean up resources, free memory, and perform final operations before object deletion.
class MyClass {
public:
MyClass() {
cout << "Constructor called" << endl;
}
~MyClass() { // Destructor
cout << "Destructor called" << endl;
}
};
MyClass obj; // Constructor called
// Destructor called automatically when obj goes out of scope
Output:
Constructor called
Destructor called
Destructor Characteristics
Automatic Call
Called automatically when object is destroyed
{
MyClass obj;
} // Destructor called here
No Parameters
Destructors cannot take parameters
~MyClass() {
// No parameters allowed
}
Resource Cleanup
Free memory and close files
~MyClass() {
delete[] data;
file.close();
}
One Per Class
Only one destructor per class
class MyClass {
~MyClass() { }
// Only one allowed
};
🔹 Basic Destructor Example
Destructors automatically clean up resources when an object goes out of scope, ensuring proper memory
management. In C++, the destructor is named ~ClassName() and is called implicitly upon
object destruction. For a Student object, the destructor might release dynamically allocated grades and
log a destruction message. The sequence shown—creation, usage, and cleanup—demonstrates RAII (Resource Acquisition
Is Initialization), where resource lifetime is tied to object lifetime. This prevents leaks, as shown by the logged
messages "Student Alice destroyed" and "Student object destroyed" after the object's scope ends.
#include <iostream>
#include <string>
using namespace std;
class Student {
public:
string name;
int* grades;
int numGrades;
// Constructor
Student(string n, int num) {
name = n;
numGrades = num;
grades = new int[numGrades]; // Allocate memory
cout << "Student " << name << " created" << endl;
}
// Destructor
~Student() {
delete[] grades; // Free allocated memory
cout << "Student " << name << " destroyed" << endl;
}
void setGrade(int index, int grade) {
if (index >= 0 && index < numGrades) {
grades[index] = grade;
}
}
void displayInfo() {
cout << "Student: " << name << endl;
cout << "Grades: ";
for (int i = 0; i < numGrades; i++) {
cout << grades[i] << " ";
}
cout << endl;
}
};
int main() {
cout << "Creating student..." << endl;
{
Student s1("Alice", 3);
s1.setGrade(0, 85);
s1.setGrade(1, 92);
s1.setGrade(2, 78);
s1.displayInfo();
} // Destructor called here when s1 goes out of scope
cout << "Student object destroyed" << endl;
return 0;
}
Output:
Creating student...
Student Alice created
Student: Alice
Grades: 85 92 78
Student Alice destroyed
Student object destroyed
🔹 Destructor with File Handling
Destructors are crucial for managing external resources like files, ensuring they are properly closed even if
an exception occurs. A FileManager class can open a file in its constructor and close it
in the destructor using std::ofstream. This guarantees that the file handle is released when the
FileManager object is destroyed, preventing data corruption or resource leaks. The example shows
writing "Hello, World!" to output.txt, followed by automatic closure logged as "File output.txt
closed". This RAII pattern is essential for robust, exception-safe file I/O operations in C++.
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
class FileManager {
private:
ofstream* outputFile;
string filename;
public:
// Constructor
FileManager(string fname) {
filename = fname;
outputFile = new ofstream(filename);
if (outputFile->is_open()) {
cout << "File " << filename << " opened successfully" << endl;
} else {
cout << "Failed to open file " << filename << endl;
}
}
// Method to write to file
void writeToFile(string content) {
if (outputFile && outputFile->is_open()) {
*outputFile << content << endl;
cout << "Written to file: " << content << endl;
}
}
// Destructor
~FileManager() {
if (outputFile) {
if (outputFile->is_open()) {
outputFile->close();
cout << "File " << filename << " closed" << endl;
}
delete outputFile;
cout << "FileManager destroyed" << endl;
}
}
};
int main() {
cout << "Creating FileManager..." << endl;
{
FileManager fm("output.txt");
fm.writeToFile("Hello, World!");
fm.writeToFile("This is a test file.");
} // Destructor called here
cout << "FileManager object destroyed" << endl;
return 0;
}
Output:
Creating FileManager...
File output.txt opened successfully
Written to file: Hello, World!
Written to file: This is a test file.
File output.txt closed
FileManager destroyed
FileManager object destroyed
🔹 Multiple Objects Example
Destructors are called in reverse order of construction when multiple objects go out of scope, maintaining
proper cleanup sequences. A static counter can track the total number of live objects. In the example,
three Counter objects are created, incrementing the total. When they exit their respective scopes,
destructors log decrements, showing "Counter 3 destroyed (Remaining: 2)" and so on. This reverse destruction order
ensures dependencies are resolved correctly—objects created later (and potentially dependent on earlier ones) are
destroyed first. It highlights how C++ manages object lifetimes automatically in stack-unwinding.
#include <iostream>
#include <string>
using namespace std;
class Counter {
private:
static int objectCount;
int id;
public:
// Constructor
Counter() {
objectCount++;
id = objectCount;
cout << "Counter " << id << " created (Total: " << objectCount << ")" << endl;
}
// Destructor
~Counter() {
cout << "Counter " << id << " destroyed (Remaining: " << objectCount - 1 << ")" << endl;
objectCount--;
}
void displayInfo() {
cout << "I am Counter " << id << endl;
}
static int getObjectCount() {
return objectCount;
}
};
// Initialize static member
int Counter::objectCount = 0;
int main() {
cout << "Creating counters..." << endl;
Counter c1;
Counter c2;
{
Counter c3;
c1.displayInfo();
c2.displayInfo();
c3.displayInfo();
cout << "Total objects: " << Counter::getObjectCount() << endl;
} // c3 destructor called here
cout << "After inner scope" << endl;
cout << "Total objects: " << Counter::getObjectCount() << endl;
return 0;
} // c1 and c2 destructors called here
Output:
Creating counters...
Counter 1 created (Total: 1)
Counter 2 created (Total: 2)
Counter 3 created (Total: 3)
I am Counter 1
I am Counter 2
I am Counter 3
Total objects: 3
Counter 3 destroyed (Remaining: 2)
After inner scope
Total objects: 2
Counter 2 destroyed (Remaining: 1)
Counter 1 destroyed (Remaining: 0)