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