Step 5 of 9
Virtual destructors
Here's why the base class of a hierarchy always has a virtual destructor. When you delete an object through a base pointer (which is what unique_ptr<Base> does when it goes away), the destructor call follows the same rule as any other member function:
- If
~Base()is not virtual, only~Base()runs. The derived part is never destroyed: its members aren't cleaned up, its resources leak. Formally, it's undefined behavior. - If
~Base()is virtual, the call goes to~Derived()first, which then automatically runs~Base().
#include <iostream>
#include <memory>
#include <string>
struct Connection {
virtual ~Connection() { std::cout << "close socket\n"; }
};
struct TlsConnection : Connection {
std::string session = "keys";
~TlsConnection() override { std::cout << "wipe " << session << "\n"; }
};
int main() {
std::unique_ptr<Connection> c = std::make_unique<TlsConnection>();
std::cout << "using connection\n";
}
using connection
wipe keys
close socket
The order
Construction goes base first, then derived: the derived constructor relies on its base part already existing. Destruction goes in reverse: derived first, then base. The output shows the derived cleanup (wipe keys) before the base cleanup (close socket).
If you removed the virtual from ~Connection, the wipe keys line would disappear. In real code that's a leaked session key, a leaked buffer, or an open file.
The rule
If a class has any virtual function, give it a virtual destructor. virtual ~Base() = default; is enough when there's nothing to clean up in the base itself.
Your turn: the program should print:
+ base
+ derived
- derived
- base
but - derived is missing. Fix it with one keyword.
Previous: Polymorphic containers Next: Calling the base version, and protected