Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A destructor is a special member function in C++ that is automatically invoked when an object’s lifetime terminates. It is responsible for executing final teardown logic immediately before the memory allocated to the object is reclaimed by the system.
class Identifier {
public:
    // Destructor declaration
    ~Identifier(); 
};

// Destructor definition
Identifier::~Identifier() {
    // Teardown logic executes here
}

// C++20 constexpr destructor example
class ConstexprIdentifier {
public:
    constexpr ~ConstexprIdentifier() {}
};

Core Characteristics and Rules

  • Lexical Syntax: The destructor’s identifier must exactly match the class name, prefixed with a tilde (~).
  • Signature Constraints: Destructors do not accept parameters and do not specify a return type (not even void).
  • Uniqueness: Because they accept no parameters, destructors cannot be overloaded. A class can possess exactly one destructor.
  • Implicit Declaration: If no user-declared destructor is provided, the C++ compiler implicitly declares a default destructor. If a destructor is user-declared but not user-defined (e.g., providing only the declaration ~Identifier();), the compiler will not implicitly declare a default destructor. An implicitly declared destructor will be defined as deleted (= delete) if any base class or non-static data member has a deleted or inaccessible destructor. Otherwise, the compiler generates a default implementation. This implicitly defined destructor performs teardown by sequentially invoking the destructors of the class’s non-static data members and base classes. It is only considered trivial (per std::is_trivially_destructible) if the destructor is not virtual, and all base classes and non-static data members also possess trivial destructors.
  • Exception Safety: In C++11 and later, destructors are implicitly noexcept(true) unless a base class or non-static data member has a destructor that is noexcept(false). If a destructor is noexcept(true), any exception that escapes it will immediately invoke std::terminate(). If a destructor is noexcept(false) (either explicitly or implicitly), an escaping exception will propagate normally, provided the destructor was not invoked during stack unwinding. However, if an exception escapes any destructor while stack unwinding is already in progress, std::terminate() is guaranteed to be invoked, aborting the program.
  • constexpr Destructors (C++20): Destructors can be declared with the constexpr specifier. This allows objects with non-trivial teardown logic to be evaluated and destroyed entirely at compile time during constant evaluation. For a destructor to be valid in a constexpr context, all non-static data members and base classes must also possess constexpr destructors.

Invocation Triggers

The C++ runtime guarantees destructor invocation under specific conditions based on the object’s storage duration and allocation method:
  1. Automatic Storage (Stack): Implicitly invoked when the execution flow exits the lexical scope in which the object was declared.
  2. Dynamic Storage (Heap): Implicitly invoked before memory deallocation when a delete expression is evaluated against a pointer referencing the object.
  3. Static/Thread Storage: Implicitly invoked during program or thread termination.
  4. Temporary Objects: Implicitly invoked at the end of the full expression in which the temporary object was created.
  5. Placement new: Explicitly invoked using the syntax ptr->~ClassName(). This is required when an object is constructed in pre-allocated memory via placement new, as the delete operator cannot be used.

Execution Order

Destruction in C++ strictly follows the reverse order of construction. When a destructor is invoked, the teardown sequence proceeds as follows:
  1. The body of the destructor executes.
  2. Non-static class member variables are destroyed in the exact reverse order of their declaration within the class definition.
  3. Direct non-virtual base class destructors are invoked in the reverse order of their appearance in the base-class list.
  4. Virtual base class destructors are invoked, but only by the destructor of the most derived class. Intermediate base class destructors do not destroy virtual bases.

Virtual and Pure Virtual Destructors

When dealing with inheritance and polymorphism, destructors in base classes must be declared virtual. If an object of a derived class is destroyed via a delete expression applied to a base-class pointer, and the base class possesses a non-virtual destructor, the behavior is strictly undefined (typically resulting in the derived destructor being bypassed and causing resource leaks).
class Base {
public:
    virtual ~Base() {} 
};

class Derived : public Base {
public:
    ~Derived() override {}
};
C++ also permits pure virtual destructors (virtual ~Base() = 0;) to force a class to be abstract without needing to declare other pure virtual functions. However, unlike standard pure virtual functions, a pure virtual destructor must still have a provided implementation body. This is because the derived class’s destructor will implicitly invoke the base class’s destructor during the standard teardown sequence.
class AbstractBase {
public:
    virtual ~AbstractBase() = 0; // Pure virtual declaration
};

// Mandatory implementation body
AbstractBase::~AbstractBase() {
    // Base teardown logic
}

The Rule of Three/Five/Zero

In C++, the presence of a user-declared destructor has profound implications for class design, governed by the Rule of Three (and subsequently the Rule of Five in C++11). Declaring a custom destructor strongly implies that the class manually manages resources. In C++11 and later, declaring a destructor explicitly suppresses the implicit generation of the move constructor and move assignment operator. Because the move operations are not generated, they cannot be used. However, the copy constructor and copy assignment operator are still implicitly generated (though relying on this behavior is deprecated in modern C++). If these implicitly generated copy operations are used, they will perform shallow copies of the managed resources, leading to critical bugs like double-free errors or memory corruption. Therefore, if a destructor must be explicitly declared, the class’s copy and move semantics must also be explicitly defined or explicitly deleted.

Explicit Control (C++11 and later)

Modern C++ allows explicit control over the compiler’s generation of destructors using the = default and = delete specifiers.
class SystemNode {
public:
    // Forces the compiler to generate the default implementation
    ~SystemNode() = default; 
};

class ImmortalObject {
public:
    // Suppresses destructor generation, preventing object destruction
    ~ImmortalObject() = delete; 
};
Marking a destructor as = delete renders the object indestructible. Consequently, the compiler will reject any attempt to allocate such an object on the stack or evaluate a delete expression against it, restricting it to dynamic allocation without standard deletion.
Master C++ with Deep Grasping Methodology!Learn More