Protected Destructor in C++ Polymorphic Base Classes
In C++, base classes designed for polymorphic use require careful lifecycle management to prevent memory leaks and undefined behavior. While the conventional approach is to declare a public virtual destructor, an alternative design pattern uses a protected, non-virtual destructor. This article examines the purpose of declaring a protected destructor on polymorphic base classes, how it enforces safe memory management at compile time, and why it is chosen over a public virtual destructor in specific performance-critical architectures.
The Problem of Polymorphic Deletion
When a class is used polymorphically, code typically interacts with derived class instances through base class pointers or references. If a client attempts to delete an object through a pointer to a base class that has a public, non-virtual destructor:
Base* obj = new Derived();
delete obj; // Undefined behavior if Base::~Base() is public and non-virtualUnder the C++ standard, deleting a derived object through a pointer to a base class without a virtual destructor results in undefined behavior. The derived class's destructor is never invoked, leading to resource leaks and incomplete cleanup.
How a Protected Destructor Solves the Problem
Declaring a base class destructor as protected prevents
unauthorized deletion through a base class pointer:
class Base {
protected:
~Base() = default;
};
class Derived : public Base {
public:
~Derived() = default;
};Because the destructor is protected, external code
cannot invoke it directly:
Base* ptr = new Derived();
delete ptr; // Compile-time error: 'Base::~Base()' is protectedThe compiler rejects the delete ptr; statement at
compile time, eliminating the possibility of accidentally triggering
undefined behavior.
At the same time, derived classes maintain full access to the base
destructor. When a Derived instance is destroyed
directly—either on the stack or via delete derivedPtr;—the
derived destructor executes and automatically invokes the protected
Base::~Base() as part of normal teardown.
Why Use a Protected Destructor Instead of a Virtual Destructor
A public virtual destructor also prevents undefined behavior during polymorphic deletion, but it introduces trade-offs:
- Elimination of vtable Overhead: A virtual
destructor requires the class to maintain a virtual method table pointer
(
vptr), which adds a memory overhead (typically 8 bytes on a 64-bit system) per object. If a class contains no other virtual functions, adding a virtual destructor forces the compiler to introduce dynamic dispatch mechanics. - Intentional Ownership Semantics: A protected destructor explicitly documents that the base class does not own the object lifecycle. It signals to users of the class that dynamic ownership should not be managed polymorphically through base pointers.
- Optimized for Mixins and CRTP: In static polymorphism, template mixins, and the Curiously Recurring Template Pattern (CRTP), objects are manipulated through base interfaces without dynamic dispatch. A protected destructor guarantees that these mixins remain lightweight and cannot be mistakenly deleted as independent entities.
Sutter’s Rule for C++ Destructors
This technique aligns with Herb Sutter's classic C++ design guideline: "Base class destructors should be either public and virtual, or protected and non-virtual."
- Use a public virtual destructor when base class
pointers are intended to manage ownership and deletion polymorphically
(e.g.,
std::unique_ptr<Base>). - Use a protected non-virtual destructor when the base class defines an interface or shared behavior, but ownership and destruction are strictly the responsibility of the derived class.