1. What is a Destructor?¶
Imagine you create an object in C++ like buying a cup. When the cup is used up, you need to clean it and put it back, right? In C++, objects also need “cleanup” when they are destroyed, which is the role of the destructor.
In simple terms:
- A constructor is a function that is automatically called when an object is created, responsible for initializing the object.
- A destructor is a function that is automatically called when an object is destroyed, responsible for cleaning up resources used by the object (e.g., dynamically allocated memory, open files, etc.).
2. Definition Format of Destructors¶
The syntax of a destructor is special. Remember these key points:
- The name must be the same as the class name, but with an additional tilde ~ at the beginning (pronounced “tilde”).
- No parameters and no return type (not even void).
- A class can only have one destructor (cannot be overloaded).
Example:
class MyClass {
public:
// Constructor (called when an object is created)
MyClass() {
cout << "Object created!" << endl;
}
// Destructor (called when an object is destroyed)
~MyClass() {
cout << "Object destroyed, resources cleaned up!" << endl;
}
};
3. Core Role of Destructors: Resource Cleanup¶
Why are destructors needed? The most common reason is to avoid resource leaks. For example:
- If an object dynamically allocates memory using new (e.g., int* p = new int;), failing to release it will cause the memory to remain occupied, leading to “memory leaks.”
- If an object opens a file (e.g., fstream file("test.txt");), failing to close the file may waste file resources or cause data loss.
Example: Dynamic Memory Cleanup
class Array {
private:
int* data; // Pointer to a dynamic array
int size; // Array size
public:
Array(int s) : size(s) {
data = new int[size]; // Allocate memory in the constructor
cout << "Constructor: Allocated memory for " << size << " ints" << endl;
}
~Array() {
delete[] data; // Release memory in the destructor to avoid leaks!
cout << "Destructor: Released array memory" << endl;
}
};
int main() {
Array arr(5); // Create an object, call the constructor
// The scope of arr is limited to main(). When main() ends, arr is destroyed, and the destructor is called.
return 0;
}
Output:
Constructor: Allocated memory for 5 ints
Destructor: Released array memory
4. When is a Destructor Called?¶
C++ automatically calls the destructor in the following scenarios (no manual trigger needed):
- Object goes out of scope: For example, a local object inside a function will have its destructor called when the function ends.
void test() {
MyClass obj; // Local object defined inside the function
// When the function ends, obj is destroyed, and the destructor is called.
}
int main() {
test(); // After test() finishes, obj's destructor is called.
return 0;
}
- Destroying a dynamic object with
delete: If an object is created withnew(i.e., a pointer to the object), the destructor is called whendeleteis used.
int main() {
MyClass* p = new MyClass(); // Dynamically create an object
delete p; // Destroy the object, call the destructor
return 0;
}
- Destruction of temporary objects: Temporary objects in function parameters or return values are destroyed when the expression ends.
MyClass createObj() {
return MyClass(); // Create a temporary object and return it; it is destroyed when the function ends.
}
int main() {
createObj(); // The temporary object is destroyed when the expression ends, calling the destructor.
return 0;
}
5. Default Destructor vs. Custom Destructor¶
If a class does not explicitly define a destructor, the compiler automatically generates a default destructor. The default destructor does nothing (it does not actively clean up resources) but will automatically call the destructors of member objects (if any).
Example: Default Destructor
class Inner {
public:
~Inner() {
cout << "Inner class object destroyed" << endl;
}
};
class Outer {
private:
Inner inner; // Member object of class Outer
public:
Outer() {
cout << "Outer class object created" << endl;
}
// No destructor defined for Outer; compiler generates a default destructor.
};
int main() {
Outer outer; // Create an Outer object
return 0; // When outer goes out of scope, the default destructor is called, which also calls Inner's destructor.
}
Output:
Outer class object created
Inner class object destroyed
6. Notes¶
- Cannot explicitly call a destructor: C++ does not allow manual invocation of a destructor (e.g.,
obj.~MyClass();), as this would cause duplicate calls and undefined behavior. - Cannot overload destructors: Since there are no parameters, destructors cannot be distinguished by parameter lists, so only one destructor is allowed per class.
- Importance of virtual destructors: If a base class pointer points to a derived class object and the base class destructor is not virtual,
deleteon the base class pointer may only call the base class destructor, leaving derived class resources uncleaned. In this case, declare the base class destructor asvirtual(virtual destructor).
Example: Virtual Destructor
class Base {
public:
virtual ~Base() { // Virtual destructor
cout << "Base destructor called" << endl;
}
};
class Derived : public Base {
public:
~Derived() {
cout << "Derived destructor called" << endl;
}
};
int main() {
Base* p = new Derived(); // Base pointer points to a Derived object
delete p; // Correct behavior: Derived destructor is called first, then Base destructor
return 0;
}
Output:
Derived destructor called
Base destructor called
Summary¶
Destructors are the “final cleanup” tool for objects in C++. Their core roles are:
- Freeing dynamically allocated memory (to avoid memory leaks).
- Closing open files or network connections.
- Cleaning up other temporary resources.
Remember: A destructor is automatically called when an object is destroyed—no manual triggering is needed. Proper use of destructors is key to avoiding resource waste and memory leaks!