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):
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:
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