Skip to content

57 Intermediate The Hundred

The Destructor That Never Ran

the socket closes; the key does not

#include <iostream>
#include <string>

struct Connection {
    ~Connection() { std::cout << "socket closed\n"; }
    virtual void send(const std::string& msg) { std::cout << "> " << msg << "\n"; }
};

struct SecureConnection : Connection {
    std::string key = std::string(4096, 'k');
    ~SecureConnection() { std::cout << "key wiped (" << key.size() << " bytes)\n"; }
    void send(const std::string& msg) override { std::cout << "> [encrypted] " << msg << "\n"; }
};

int main() {
    Connection* c = new SecureConnection();   // one connection, opened securely
    c->send("hello");
    delete c;
}

Run it. Which destructors run?

Answer

Only ~Connection — the key is never wiped, and its 4 KB are never freed. Typical output (GCC 11 on x86-64 Linux, identical from -O0 to -O3):

> [encrypted] hello
socket closed

Why

delete c picks the destructor from the pointer's static type unless that destructor is virtual — and ~Connection is not, so ~SecureConnection never runs and the string it owns is never freed (valgrind: "4,097 bytes in 1 blocks are definitely lost"). Formally it is worse than a leak: deleting a derived object through a base pointer with a non-virtual destructor is undefined behavior, and GCC also hands operator delete the base's size, 8 bytes instead of 40 — -fsanitize=address aborts on that as new-delete-type-mismatch. -Wall flags the raw delete as "might cause undefined behavior" (-Wdelete-non-virtual-dtor), yet the same leak inside a std::unique_ptr<Connection> passes in silence, its delete buried in a system header — while std::shared_ptr<Connection> gets this right even here, capturing a deleter for the type it was handed.

The fix

One keyword, and the derived destructor runs first — key wiped, then socket closed:

virtual ~Connection() { std::cout << "socket closed\n"; }

If a base is never meant to be deleted polymorphically, say so in code: a protected non-virtual destructor makes delete c fail to compile instead of leaking.

Takeaway: if it can be deleted through a base pointer, its destructor must be virtual.

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

Open in Compiler Explorer ↗ Quiz this entry