Language[lang]

Destructor in C++: what it is and when it runs

Section
Language [lang]
Standard
C++17
Compilers
MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2, Clang 23.1 (libc++)
Published
Length
7 min read
In this answer6 clauses
  1. 1When is a destructor called?
  2. 2In what order are objects destroyed?
  3. 3What is a destructor for?
  4. 4Do you need to write a destructor?
  5. 5What is a virtual destructor?
  6. 6Common destructor mistakes

Abstract

A destructor in C++ is the member function, written ~Tracer() for a class Tracer, that runs automatically when an object’s life ends: at the closing brace for a local, at delete for an object made with new, after main returns for a global. Locals, the members of an object and globals are destroyed in the reverse of the order they were made. Most classes need no destructor written, because their members clean up after themselves; a base class whose objects are deleted through a base pointer needs a virtual one.

Two tall curly braces, the opening one black, the closing one blue. Between them, a box of two hatched compartments; past the closing brace, the same box as an empty, dashed grey outline.
Figure 1 — An object’s life ends at the closing brace of its block, and that is when its destructor runs.

A destructor in C++ is the member function that runs automatically when an object’s life ends: ~File() for a class named File. It takes no arguments and returns nothing, and its job is to release what the object owns. A local object’s destructor runs when execution leaves the block it was created in, usually at its closing brace.

Tracer, the example used all the way through, is a struct whose destructor prints the object’s name, so each sample shows the moment it ran:

[Example 1:
#include <iostream>
#include <string>

struct Tracer {
    std::string name;
    ~Tracer() { std::cout << "~" << name << '\n'; }  // the destructor
};

int main() {
    {
        Tracer local{"local"};
        std::cout << "before the closing brace\n";
    }  // local's life ends here: its destructor runs
    std::cout << "after the closing brace\n";
}

— end example]

It printed:

[Example 2:
before the closing brace
~local
after the closing brace

— end example]

Nothing in main calls ~Tracer(). The destructor ran at the inner block’s closing brace, between the two lines.

The samples are C++17, built with MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2 and Clang 23.1 (libc++). All four printed the same lines, apart from the one difference shown under the vector below. Since When lists which compiler releases support each standard.

When is a destructor called?

A destructor is called when its object’s life ends, and when that happens depends on how the object was made:

The object Its destructor runs
A local variable at the closing brace of its block
Made with new when delete is called on it, never on its own
A member of a class when the object that holds it is destroyed
An element of a std::vector when it is removed, or when the vector is destroyed
A global or a static after main returns

Three of those cases in one program:

[Example 3:
int main() {
    Tracer* heap = nullptr;
    {
        Tracer local{"local"};
        heap = new Tracer{"heap"};
        std::cout << "closing the block\n";
    }  // ~local runs here; heap's object lives on
    std::cout << "after the block\n";
    delete heap;  // ~heap runs here, and only here

    {
        std::vector<Tracer> names(2);  // two elements, made in place
        names[0].name = "names[0]";
        names[1].name = "names[1]";
        std::cout << "closing the vector's block\n";
    }  // the vector is destroyed, and with it both elements
    std::cout << "end of main\n";
}

— end example]

Microsoft’s library (under MSVC and Clang 19.1) and GCC’s libstdc++ printed:

[Example 4:
closing the block
~local
after the block
~heap
closing the vector's block
~names[0]
~names[1]
end of main

— end example]

~heap waited for the delete, after its block had closed. libc++ printed the same lines, except that it destroyed names[1] before names[0]: the order in which a container destroys its elements is the library’s choice, so do not write code that depends on it.

Globals and static objects outlive main:

[Example 5:
Tracer global{"global"};  // made before main starts

void helper() {
    static Tracer kept{"static"};  // made on the first call
}

int main() {
    helper();
    Tracer local{"local"};
    std::cout << "last line of main\n";
}  // ~local here; ~static and ~global after main returns

— end example]

It printed:

[Example 6:
last line of main
~local
~static
~global

— end example]

Objects that live for the whole run, globals and static locals, are destroyed after main returns ([basic.start.term]). kept, made after global, was destroyed before it.

In what order are objects destroyed?

Locals in one block, the parts of one object, and globals and statics are destroyed in the reverse of the order they were created. Two locals made as a, then b, are destroyed b first. Inside one object, the destructor’s body runs first, then the members in the reverse of the order they are declared, then the base class (destruction sequence):

[Example 7:
void two_locals() {
    Tracer a{"a"};
    Tracer b{"b"};
}  // ~b, then ~a: the reverse of the order they were made

struct Base {
    ~Base() { std::cout << "~Base\n"; }
};

struct Report : Base {
    Tracer first{"first"};    // declared first
    Tracer second{"second"};  // declared second
    ~Report() { std::cout << "~Report body\n"; }
};

int main() {
    two_locals();
    std::cout << "--\n";
    {
        Report report;
    }  // ~Report body, ~second, ~first, ~Base
}

— end example]

It printed:

[Example 8:
~b
~a
--
~Report body
~second
~first
~Base

— end example]

Report inherits from Base and has two members, first and second. The figure draws such an object as four rows, the base class on top. Building it runs down the rows: the base class first, then the members in the order they are declared, then the constructor’s body (initialization order). Destroying it runs back up.

An object drawn as four rows: base class, first member, second member, then constructor body beside destructor body. The built arrow on the left counts 1 to 4 downward; the destroyed arrow on the right counts 1 to 4 upward.
Figure 2 — An object is taken apart in exactly the reverse of the order it was put together.

Because the body runs before any member is destroyed, a destructor can still use every member of its class. C++ constructors cover the building half.

What is a destructor for?

A destructor gives back what the object holds: it closes a file, releases a lock, frees memory. It runs on every way out of a block, an early return and a caught exception included, so the cleanup cannot be forgotten. Here Tracer stands in for an open file:

[Example 9:
bool save(bool disk_full) {
    Tracer file{"save.txt"};
    if (disk_full) {
        std::cout << "disk full, returning early\n";
        return false;  // ~save.txt runs on the way out
    }
    std::cout << "saved\n";
    return true;
}

void load() {
    Tracer file{"load.txt"};
    std::cout << "throwing\n";
    throw std::runtime_error("load failed");  // ~load.txt runs too
}

int main() {
    save(true);
    try {
        load();
    } catch (const std::runtime_error& e) {
        std::cout << "caught: " << e.what() << '\n';
    }
}

— end example]

It printed:

[Example 10:
disk full, returning early
~save.txt
throwing
~load.txt
caught: load failed

— end example]

~save.txt printed on the early return, and ~load.txt before the catch block ran. Tying a resource to the life of an object this way is called RAII, short for resource acquisition is initialization (RAII on cppreference).

Do you need to write a destructor?

Usually not. Members such as std::string, std::vector and std::unique_ptr clean up after themselves, and the destructor the compiler writes for your class runs theirs:

[Example 11:
struct Player {
    std::string name = "Ada";
    std::vector<int> scores{10, 20, 30};
    Tracer tracer{"tracer"};
    // No destructor written: the compiler's own one destroys
    // tracer, scores and name, in that order.
};

int main() {
    {
        Player player;
        std::cout << player.name << " has " << player.scores.size()
                  << " scores\n";
    }  // ~tracer: the member's destructor still runs
    std::cout << "after the block\n";
}

— end example]

It printed:

[Example 12:
Ada has 3 scores
~tracer
after the block

— end example]

Leaving the destructor, the copies and the moves to the compiler is the rule of zero. Write a destructor only when the class itself holds something no member cleans up, such as an integer handle from a C library; a pointer from new belongs in a std::unique_ptr member (C++ Core Guidelines C.30). Such a class then needs its copy and move operations defined or deleted too (C.21), which the rule of zero article walks through.

What is a virtual destructor?

A virtual destructor is a base class’s destructor declared virtual, so that delete through a pointer to the base runs the derived class’s destructor too. Without it, that delete is undefined behavior: the standard sets no rule for what the program does ([expr.delete]). A base class whose objects are deleted that way needs one, and = default is enough:

[Example 13:
struct Base {
    virtual ~Base() = default;
};

struct Derived : Base {
    ~Derived() override { std::cout << "~Derived\n"; }
};

int main() {
    Base* raw = new Derived;
    delete raw;  // ~Derived runs, then Base's destructor

    std::unique_ptr<Base> owned = std::make_unique<Derived>();
    std::cout << "end of main\n";
}  // owned deletes its Derived: ~Derived again

— end example]

It printed:

[Example 14:
~Derived
end of main
~Derived

— end example]

Polymorphism in C++ has the longer version, with a base class whose destructor prints too.

Common destructor mistakes

  • Calling local.~Tracer() yourself on a local: the destructor runs again at the closing brace, and that second call is undefined behavior (destructors on cppreference).
  • Forgetting delete: an object made with new lives until delete is called on it, so its destructor never runs (new expression on cppreference). Hold it in a std::unique_ptr, which calls delete for you.
  • Calling delete twice on the same pointer: after the first, it no longer points to an object made by new, so the second is undefined behavior (delete on cppreference).
  • Letting an exception leave a destructor. Destructors are noexcept unless you say otherwise, so an exception that leaves one calls std::terminate, which ends the program ([except.terminate]); can a C++ destructor throw? has the rest.

The Language section has the other answers on classes and objects.

Questions

Can a destructor take parameters or be overloaded?

No. A destructor takes no parameters, so it cannot be overloaded on them, and the compiler selects exactly one destructor for each class. (Since C++20 a class template can declare several with different requires-clauses; the one whose constraints hold is used.) A second destructor taking an int, declared beside ~Tracer(), failed to compile on all four compilers used here: Clang said “destructor cannot have any parameters”, and GCC added that the destructor cannot be overloaded.

Is a destructor called automatically in C++?

Yes: for a local variable at the end of its block, for members and container elements when the object holding them is destroyed, and for globals and static objects after main returns. An object made with new is the exception: it is never destroyed automatically, and its destructor runs when delete is called on it.

Does delete call the destructor?

Yes. delete first runs the destructor of the object the pointer points to, then frees the memory new gave it. Deleting a derived object through a pointer to its base class needs a virtual destructor in the base, or that delete is undefined behavior.

What is the difference between a constructor and a destructor?

A constructor sets an object up when its life begins; the destructor cleans up when it ends. A class can have several constructors, told apart by their parameters, but one destructor, written ~Tracer() for a class Tracer, which takes none.

Can a destructor throw an exception?

It can, but by default an exception that leaves a destructor calls std::terminate, which ends the program: since C++11 a destructor is noexcept unless it is declared noexcept(false) or a member’s or base’s destructor is. std::is_nothrow_destructible_v was true for a class whose destructor prints, on all four compilers used here, and false for one declared noexcept(false). The C++ Core Guidelines put the rule as: a destructor must not fail.

Why does my destructor run twice?

Usually because there are two objects: every copy and every temporary has a destructor call of its own. names.push_back(Tracer{“a”}) printed ~a twice on all four compilers used here, once for the temporary at the end of that statement and once for the vector’s copy of it when the vector was destroyed.

Sources

  1. Destructorscppreference.com
  2. Constructors and member initializer listscppreference.com
  3. RAIIcppreference.com
  4. delete expressioncppreference.com
  5. [class.dtor] DestructorsC++ standard working draft
  6. [basic.start.term] TerminationC++ standard working draft
  7. [expr.delete] DeleteC++ standard working draft
  8. C++ Core Guidelines: Constructors, assignments, and destructorsC++ Core Guidelines

Read next

  1. Can a C++ destructor throw? What happens since C++11Language · Miriam Castellanos · written for C++20
  2. C++ try catch: how to throw and catch exceptionsLanguage · Miriam Castellanos · written for C++17
  3. C++ while loop (and do-while): syntax and examplesLanguage · Miriam Castellanos · written for C++17