If a class is intended to be used polymorphically, with derived instances being stored as base pointers/references, its base class' destructor should be either virtual
or protected
. In the former case, this will cause object destruction to check the vtable
, automatically calling the correct destructor based on the dynamic type. In the latter case, destroying the object through a base class pointer/reference is disabled, and the object can only be deleted when explicitly treated as its actual type.
struct VirtualDestructor {
virtual ~VirtualDestructor() = default;
};
struct VirtualDerived : VirtualDestructor {};
struct ProtectedDestructor {
protected:
~ProtectedDestructor() = default;
};
struct ProtectedDerived : ProtectedDestructor {
~ProtectedDerived() = default;
};
// ...
VirtualDestructor* vd = new VirtualDerived;
delete vd; // Looks up VirtualDestructor::~VirtualDestructor() in vtable, sees it's
// VirtualDerived::~VirtualDerived(), calls that.
ProtectedDestructor* pd = new ProtectedDerived;
delete pd; // Error: ProtectedDestructor::~ProtectedDestructor() is protected.
delete static_cast<ProtectedDerived*>(pd); // Good.
Both of these practices guarantee that the derived class' destructor will always be called on derived class instances, preventing memory leaks.