Skip to content

79 Advanced The Hundred

The Object That Was Never Destroyed

cleanup that only half happens

struct Trace {
    ~Trace() { std::cout << "trace off\n"; }
};

struct Session {
    Trace trace;
    char* buffer;

    Session() {
        buffer = new char[4096];
        std::cout << "buffer acquired\n";
        throw std::runtime_error("handshake timed out");
    }
    ~Session() {
        delete[] buffer;
        std::cout << "buffer released\n";
    }
};

int main() {
    try {
        Session s;
    } catch (const std::exception& e) {
        std::cout << "caught: " << e.what() << "\n";
    }
}

Run it. Which cleanup lines appear?

Answer

trace off prints; ~Session never runs, so no buffer released — the 4096 bytes leak.

Why

A destructor undoes a constructor that finished; throw halfway and the object never officially existed, so ~Session is never called. What does get unwound is every sub-object already fully built — trace is destroyed in reverse order of construction, which is why trace off prints. Anything the constructor body grabbed by hand is therefore orphaned: -fsanitize=address reports 4096 byte(s) leaked in 1 allocation(s). A function-try-block is no rescue: its handler runs after the members are gone, and rethrows. -Wall -Wextra is silent; nothing here is undefined, it is merely leaky.

The fix

Let a member own the resource, so partial construction cleans up after itself:

struct Session {
    Trace trace;
    std::vector<char> buffer;   // a member: destroyed even when the ctor throws
    Session() : buffer(4096) { throw std::runtime_error("handshake timed out"); }
    // no destructor needed at all — the Rule of Zero earning its keep
};

Takeaway: a throwing constructor destroys the members, never the object.

Try it: g++ -std=c++17 main.cpp -o demo && ./demo

Open in Compiler Explorer ↗ Quiz this entry