When should virtual destructors be used in object-oriented programming?
I have a solid understanding of most OOP theory but the one thing that confuses me a lot is virtual destructors. I thought that the destructor always gets called no matter what and for every object in the chain. When are you meant to make them virtual and why?
Virtual destructors should be used in object-oriented programming whenever you have a base class with virtual functions and you plan to delete derived class objects through base class pointers. This ensures proper cleanup by calling the most-derived class destructor first and then working up the inheritance chain. Without virtual destructors, you’ll encounter undefined behavior, resource leaks, and incomplete object destruction when working with polymorphism.
Contents
- Understanding Virtual Destructors
- When to Use Virtual Destructors
- Technical Behavior Explained
- Common Mistakes and Pitfalls
- Performance Considerations
- When NOT to Use Virtual Destructors
Understanding Virtual Destructors
A virtual destructor is a special type of destructor in object-oriented programming that follows the same principles as other virtual functions. When declared virtual in a base class, it ensures that the destructor of the most-derived class is called first, followed by destructors of all base classes in reverse order of construction.
The fundamental purpose of virtual destructors is to solve the problem of proper cleanup in polymorphic hierarchies. As GeeksforGeeks explains, “Deleting a derived class object using a pointer of base class type that has a non-virtual destructor results in undefined behavior.”
When to Use Virtual Destructors
The Golden Rule
Any time you have a virtual function in a class, you should immediately add a virtual destructor (even if it does nothing). This is the most important guideline to remember according to GeeksforGeeks.
Specific Scenarios Requiring Virtual Destructors:
-
Polymorphic Classes with Inheritance
When your base class has virtual functions and you intend to create derived classes that will be managed through base class pointers. -
Factory Patterns and Object Creation
When objects are created at runtime and their exact type isn’t known until execution time. -
Interface Classes
When defining abstract base classes that serve as interfaces for multiple implementations. -
Plugin Systems
When loading and unloading dynamically loaded modules or plugins.
Code Example
class Base {
public:
virtual void someVirtualFunction() {} // Virtual function present
virtual ~Base() {} // Virtual destructor required
};
class Derived : public Base {
private:
int* data;
public:
Derived() : data(new int[100]) {}
~Derived() { delete[] data; } // Cleanup happens properly
};
In this example, without the virtual destructor, deleting a Derived object through a Base* pointer would only call Base::~Base(), causing a memory leak from the int[100] array.
Technical Behavior Explained
What Happens Without Virtual Destructors
When you delete a derived class object through a base class pointer without a virtual destructor:
Base* basePtr = new Derived();
delete basePtr; // Problem!
In this case:
- Only
Base::~Base()is called Derived::~Derived()is never invoked- Resources allocated by the derived class are not released
- This results in undefined behavior according to the SEI CERT C++ Coding Standard
What Happens With Virtual Destructors
With a virtual destructor:
- The runtime system looks up the destructor in the virtual table (vtable)
Derived::~Derived()is called first- After
Derived::~Derived()completes,Base::~Base()is called automatically - All resources are properly cleaned up in reverse order of construction
As explained on Stack Overflow: “So if the destructor is virtual, this does the right thing: it calls Derived::~Derived first, which then automatically calls Base::~Base when it’s done.”
Common Mistakes and Pitfalls
The “Destructor Always Called” Misconception
You mentioned thinking that “the destructor always gets called no matter what and for every object in the chain.” This is partially true, but the key insight is: without virtual destructors, the wrong destructor gets called.
- The destructor does get called for the static type of the pointer
- But it doesn’t call the destructor for the actual object type (dynamic type)
- This leads to incomplete cleanup and resource leaks
Memory Leak Example
Consider this classic example:
class Animal {
public:
Animal() { cout << "Animal constructor" << endl; }
~Animal() { cout << "Animal destructor" << endl; } // NOT virtual!
};
class Dog : public Animal {
private:
char* name;
public:
Dog() : name(new char[10]) { cout << "Dog constructor" << endl; }
~Dog() {
delete[] name; // This cleanup won't happen!
cout << "Dog destructor" << endl;
}
};
int main() {
Animal* animal = new Dog();
delete animal; // Only Animal destructor called!
return 0;
}
Output without virtual destructor:
Animal constructor
Dog constructor
Animal destructor
Notice: The Dog destructor and delete[] name never execute!
Output with virtual destructor:
Animal constructor
Dog constructor
Dog destructor
Animal destructor
Performance Considerations
Virtual Function Overhead
Virtual destructors do introduce some performance overhead:
- Slower calls: Virtual function calls require additional instructions to access the vtable and look up the correct function
- Memory overhead: Each object with virtual functions contains a vtable pointer (typically 8 bytes on 64-bit systems)
Optimization Opportunities
However, modern compilers are quite sophisticated:
- Static optimization: If the compiler knows the exact type at compile time, it can replace virtual calls with direct function calls
- Inlining: Virtual destructors can often be inlined
- Minimal impact: In most applications, this overhead is negligible compared to the benefit of correct resource management
As noted in the BulldogJob article: “The compiler/linker pairs can optimize virtual destructor calls that are invoked on the true class type and replace them with ordinary calls, only if the optimization level is high enough.”
When NOT to Use Virtual Destructors
Cases Where Virtual Destructors Are Unnecessary:
-
Non-Polymorphic Classes
If your class doesn’t have virtual functions and won’t be used polymorphically. -
Final Classes
Classes that are explicitly designed not to be inherited from (can usefinalkeyword in C++11 and later). -
Standard Library Classes
Most standard library classes likestd::string,std::vector, etc., don’t have virtual destructors because they’re not meant for subclassing.
Alternative: Protected Destructor
For classes that should be inherited from but not deleted through base pointers, consider a protected destructor:
class BaseClass {
protected:
~BaseClass() {} // Protected, not virtual
// Allow deletion only by derived classes
};
class DerivedClass : public BaseClass {
public:
~DerivedClass() {} // This can call the protected destructor
};
As Software Engineering Stack Exchange explains: “Likewise, even if a class is designed to be inherited but you never delete subtype instances through a base pointer, then it also does not require a virtual destructor.”
Conclusion
Key Takeaways:
- Virtual destructors are essential for proper cleanup in polymorphic hierarchies
- Always make destructors virtual when your class has virtual functions
- Without virtual destructors, deleting derived objects through base pointers causes undefined behavior and resource leaks
- The performance overhead is usually worth the safety benefit
- Not all classes need virtual destructors—use them judiciously
Practical Recommendations:
- Follow the “virtual function → virtual destructor” rule consistently
- Use virtual destructors in all base classes that might be part of a polymorphic hierarchy
- Consider protected destructors for classes that should be inherited but not polymorphically deleted
- Document your design choices regarding virtual destructors for maintainability
Virtual destructors might seem confusing at first, but they’re a fundamental safety mechanism in object-oriented programming that prevents subtle and dangerous bugs. Once you understand the problem they solve—proper cleanup in polymorphic scenarios—you’ll recognize when they’re needed and why they’re essential.
Sources
- Virtual Destructor - GeeksforGeeks
- When to Use Virtual Destructors in C++? - GeeksforGeeks
- When to use virtual destructors - Stack Overflow
- Virtual destructor in polymorphic classes - Stack Overflow
- OOP52-CPP. Do not delete a polymorphic object without a virtual destructor - SEI CERT C++ Coding Standard
- Virtual Destructors in C++. Necessity, Good Practice, Bad Practice - BulldogJob
- When NOT to use virtual destructors? - Software Engineering Stack Exchange
- Polymorphism & Destructors - Riptutorial C++
- Understanding Virtual Destructors in C++ - Medium
- Virtual Destructors in C++ - YouTube