Page Content

Tutorials

Destructors In C++: The Cleanup Code For Your Objects

Destructors in C++

Destructors in C++ are unique member functions of a class that are automatically called upon when a class object is destroyed. They function in the opposite direction as constructors, making them its “complement”.

Role and Purpose of Destructors

Destructors mostly free up resources an object has allocated. Avoiding resource leaks from opened files or constructor-allocated memory is crucial. Destructors release resources when an object dies, restoring the system to a stable state. The C++ Resource Acquisition Is Initialization (RAII) idiom relates resource acquisition to object lifetime to manage resources properly whether a function exits normally or with an exception.

A class that needs a destructor to manage resources may need a user-defined copy constructor and copy-assignment operator. This is “The Rule of Three/Five.”

Definition and Syntax of Destructors

Any member function with the same name as its class but followed by a tilde (~) is referred to as a destructor. Among the main traits of destructors are:

  • Not even void, they have no return value.
  • Parameters are not taken by them.
  • Overloading them is impossible because they don’t accept any parameters.
  • A given class always has a single destructor.
  • In a class, destructors are usually declared in the public section.
  • The members are destroyed when the function body has been executed in a destructor. In the opposite sequence from when they were first created, members are destroyed. There is no action taken to destroy members of built-in types since they lack destructors.

Synthesized Destructor: If a class doesn’t declare its own destructor directly, the compiler will define a synthesized one for it. This synthesized destructor usually contains a function body that is empty. For members of class type, its own destructors are immediately triggered; its main function is to control the implicit destruction of the object’s data members. However, a pointer data member will not be deleted by the synthesised destructor.

Explicitly Defaulted Destructor: The default destructor can be explicitly requested from the compiler using = default starting with C++11. This is especially helpful for explicitly designating a virtual destructor as synthesized in inheritance hierarchies, which affects move operations.

Deleted Destructor: To stop the destruction of objects of that type, a destructor can be explicitly removed with = delete. Because destructors cannot be destroyed, the compiler will not allow the construction of variables or temporaries of that type if one is removed.

When Destructors are Called

In several situations, destructors are automatically triggered:

  • Automatic Objects (Stack-allocated): The destructor is invoked when a local object exits scope, for example, at the end of the code block in which it was defined. The destructors of any declared objects are invoked at the conclusion of each loop iteration, in the opposite order that they were created.
  • Dynamically Allocated Objects (Heap-allocated): The destructor of an object’s pointer is called before the delete operator releases memory. In arrays allocated using new[], delete[] ensures each element calls the destructor.
  • Global or Static Objects: The program’s destructors are invoked at the conclusion of its execution.
  • Temporary Objects: Destroyed after the whole expression in which they were produced was complete.
  • Objects Passed/Returned by Value: When the function finishes, the copy’s destructor is called.

The item itself must be destroyed, not a reference or pointer that goes out of scope.

Destructors in Inheritance Hierarchies

Destructors “tear down” an object in the opposite order from constructors, which build an object “from the bottom up” (base before member, then member before derived) in a class hierarchy. The derived class destructor body is followed by its member destructors and base class destructors.

Even without explicit work, base classes must define a virtual destructor. With polymorphism, this is essential. Upon deleting a dynamically allocated object of a derived type via a pointer to its base class, a virtual destructor makes sure that the appropriate derived class destructor is called, followed by all base class destructors. Since only the destructor of the base class would be called in the absence of a virtual destructor, the derived object’s cleaning would be insufficient (for example, resources allocated solely by the derived class might not be released).

Destructors and Exception Handling

During exception handling, especially stack unwinding, destructors are essential. Resources are appropriately released when an exception is thrown because destructors for objects created in the try block are automatically triggered once these objects exit scope. Destructors should never, however, throw exceptions about things they don’t handle locally. When a destructor throws an exception during stack unwinding, the program will end if the exception is not captured by the destructor.

Code Example Demonstrating Destructors

#include <iostream> // For input/output operations 
#include <string>   // For using std::string
class MyClass {
public:
    std::string name;
    // Parameterized constructor 
    MyClass(const std::string& n) : name(n) {
        // Constructor initializes the object and prints a message 
        std::cout << "Constructor for " << name << " called. Object created." << std::endl;
    }
    // Destructor definition 
    ~MyClass() {
        // Destructor performs cleanup (e.g., releasing memory)
        // In this simple example, it just prints a message
        std::cout << "Destructor for " << name << " called. Object destroyed." << std::endl;
    }
};
// Global object: Its destructor will be called when the program terminates 
MyClass globalObject("the global object");
int main() {
    std::cout << "\n--- Starting main() function. ---" << std::endl;
    // Local object 1: Its destructor is called when 'main' function ends 
    MyClass localObject1("the first local object (in main)");
    { // Start of an inner block to demonstrate scope
        std::cout << "\n--- Entering inner block. ---" << std::endl;
        // Local object 2: Its destructor is called when this inner block ends 
        MyClass localObject2("the second local object (in inner block)");
        std::cout << "Last statement within the inner block." << std::endl;
    } // localObject2 goes out of scope here; its destructor is automatically invoked.
    std::cout << "\n--- Back in main() after inner block. ---" << std::endl;
    // Dynamically allocated object: Its destructor is called only when 'delete' is used 
    MyClass* dynamicObject = new MyClass("the dynamic object");
    std::cout << "Explicitly deleting the dynamic object." << std::endl;
    delete dynamicObject; // Explicitly calls the destructor for 'dynamicObject' 
    std::cout << "\n--- Last statement in main(). ---" << std::endl;
    return 0;
} // localObject1 goes out of scope here; its destructor is automatically invoked.
// After main() completes, 'globalObject' destructor is automatically invoked.

Output

Constructor for the global object called. Object created.
--- Starting main() function. ---
Constructor for the first local object (in main) called. Object created.
--- Entering inner block. ---
Constructor for the second local object (in inner block) called. Object created.
Last statement within the inner block.
Destructor for the second local object (in inner block) called. Object destroyed.
--- Back in main() after inner block. ---
Constructor for the dynamic object called. Object created.
Explicitly deleting the dynamic object.
Destructor for the dynamic object called. Object destroyed.
--- Last statement in main(). ---
Destructor for the first local object (in main) called. Object destroyed.
Destructor for the global object called. Object destroyed.

The sequence of calls to constructors and destructors for global, local (stack-allocated), and dynamically allocated (heap-allocated) objects is easily demonstrated by this output. Destructors are called in the same scope but in reverse order of construction, and only when delete is explicitly used for dynamically allocated objects.

You can also read Default And Parameterized Constructors In C++ With Examples

Agarapu Geetha
Agarapu Geetha
My name is Agarapu Geetha, a B.Com graduate with a strong passion for technology and innovation. I work as a content writer at Govindhtech, where I dedicate myself to exploring and publishing the latest updates in the world of tech.
Index