When you call delete on a pointer, the destructor of the most-derived object (type D) need be invoked; however the compiler only knows how to invoke the destructor of the static type (B) it sees at the moment, so if those types do not match (B != D), then B need have a virtual destructor.
This has several implications:
void stack_allocated() {
MostDerived md; // no need for a virtual destructor,
... // the static type (MostDerived) is the actual type.
}
void dynamically_allocated() {
Derived* d = new MostDerived;
delete d; // Derived needs a virtual destructor,
// because Derived != MostDerived
// Bases of Derived need no virtual destructor
}
A simplification of the rule is to say:
- only inherit from class which have
virtual methods
- as soon as a class has a
virtual method, its destructor should be private, protected or virtual
which is generally what is taught (much easier to remember) even though it is sometimes too simplistic.
-Wnon-virtual-dtor matches this simplification.
Note: there is usually no penalty to making the destructor virtual if the class already has virtual methods, because current implementations use a virtual-pointer to a single static table which regroups all the methods of the class. Furthermore, the compiler may be able to devirtualize the call if it is able to predict the dynamic type in which case it does not even have a runtime cost.
There is an alternative though: I developed -Wdelete-non-virtual-dtor to warn not at the declaration site, but at the call site (delete here). As a result, it has no false positives (providing you use final to mark leaf classes), but warns later. YMMV.
Note: for example, std::shared_ptr<Derived> p = std::make_shared<MostDerived>(); is fine even without a virtual destructor because std::make_shared creates a Deleter based on the actual type created; this is also why you can do std::shared_ptr<void> p = ...; without issues.
Regarding the IBM link you provide:
- the discussion is focused on abstract classes thus they should the minimum amount of code that creates an abstract class. Note, for example, how the type names are meaningless, which is arguably very bad in production code!
- The class is perfectly usable as-is, only if someones calls
delete on a A* will you run into trouble.