Question
In C++, I understand most object-oriented programming concepts, but virtual destructors are still confusing to me.
I thought a destructor is always called for every object in the inheritance chain when an object is destroyed.
When should a destructor be declared virtual, and why is that necessary?
Short Answer
By the end of this page, you will understand what a virtual destructor does in C++, why it matters for inheritance and polymorphism, and exactly when you should use it. You will also see what goes wrong when a base class destructor is not virtual and an object is deleted through a base-class pointer.
Concept
In C++, destructors are responsible for cleanup when an object is destroyed. That cleanup may include:
- releasing memory
- closing files
- freeing sockets or handles
- undoing ownership of other resources
A destructor does get called when an object is destroyed, but the important detail is:
- Which destructor gets called first
- Whether C++ knows the real dynamic type of the object
If you destroy an object directly, C++ knows its full type and will call the full destruction chain.
Derived d;
When d goes out of scope, C++ destroys it correctly:
Derived::~Derived()runs- then
Base::~Base()runs
That part is automatic.
The problem happens when you use polymorphism, especially when deleting through a base-class pointer.
Base* ptr = new Derived();
delete ptr;
If Base does not have a virtual destructor, deleting through ptr is undefined behavior. In practice, this often means only the base destructor runs, and the derived cleanup may be skipped.
That is dangerous because the derived class may own resources that must be released.
A virtual destructor tells C++:
Mental Model
Think of a base-class pointer as a label on a box.
- The label says
Base* - But the box may actually contain a
Derivedobject
When you destroy the object through that label, C++ needs to know whether to open the box based only on the label or based on what is really inside.
- Without a virtual destructor, C++ may act as if the box only contains a
Base - With a virtual destructor, C++ checks the real object type and performs the full cleanup
Another way to think about it:
- A non-virtual destructor is like following a cleanup checklist for the base part only
- A virtual destructor is like asking, "What object is this really?" and then running the full checklist for the derived object first, then the base object
Syntax and Examples
The key syntax is simple:
class Base {
public:
virtual ~Base() = default;
};
Example without a virtual destructor
#include <iostream>
using namespace std;
class Base {
public:
~Base() {
cout << "Base destructor\n";
}
};
class Derived : public Base {
public:
~Derived() {
cout << "Derived destructor\n";
}
};
int main() {
Base* ptr = new Derived();
delete ptr;
}
Problem
This is undefined behavior because Base does not have a virtual destructor.
You might see only:
Base destructor
Step by Step Execution
Consider this example:
#include <iostream>
using namespace std;
class Animal {
public:
virtual ~Animal() {
cout << "Animal destroyed\n";
}
};
class Dog : public Animal {
public:
~Dog() override {
cout << "Dog destroyed\n";
}
};
int main() {
Animal* pet = new Dog();
delete pet;
}
Here is what happens step by step:
-
Animal* pet = new Dog();- Memory is allocated for a
Dogobject. - The pointer type is
Animal*, but the actual object isDog.
- Memory is allocated for a
-
delete pet;- C++ sees that points to a polymorphic base type.
Real World Use Cases
Virtual destructors are used whenever a base class is meant to represent multiple concrete implementations.
1. Interface-style base classes
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0;
};
Then derived classes like Circle and Rectangle can be handled through Shape* or std::unique_ptr<Shape> safely.
2. Plugin or framework systems
A framework may return objects as pointers or references to a base type:
Plugin* plugin = loadPlugin();
delete plugin;
If Plugin is a base class, its destructor must be virtual.
3. Resource-owning derived classes
A derived class may manage files, buffers, sockets, or dynamic memory:
: Logger {
};
Real Codebase Usage
In real projects, virtual destructors usually appear in base classes that define a polymorphic API.
Common pattern: polymorphic base class
class Service {
public:
virtual ~Service() = default;
virtual void start() = 0;
virtual void stop() = 0;
};
This is common in:
- service abstractions
- repository patterns
- rendering backends
- parser interfaces
- strategy objects
Common pattern: defaulted virtual destructor
Most codebases write this:
virtual ~Base() = default;
Why?
- it communicates intent clearly
- it avoids writing an empty destructor body
- it still makes destruction polymorphic
Pattern: abstract base classes
If a class already has pure virtual functions, it is often used through base pointers or references. That strongly suggests the destructor should be virtual too.
Common Mistakes
1. Assuming destructors are always polymorphic
Beginners often think this is always safe:
class Base {
public:
~Base() {}
};
class Derived : public Base {
public:
~Derived() {}
};
Base* ptr = new Derived();
delete ptr; // wrong
It is not safe. The base destructor must be virtual.
2. Having virtual methods but a non-virtual destructor
Broken design:
class Animal {
public:
virtual void speak() {}
~Animal() {}
};
This class is polymorphic, so it is very likely to be used through Animal*. The destructor should usually be:
virtual ~Animal() = default;
3. Thinking delete only frees memory
Comparisons
| Situation | Virtual destructor needed? | Why |
|---|---|---|
| Class is never used as a base class | Usually no | No polymorphic deletion is expected |
| Base class with virtual functions | Usually yes | Strong sign that objects will be handled polymorphically |
Object deleted through Base* | Yes | Required for correct derived destruction |
Object created and destroyed directly as Derived | No for this specific case | C++ already knows the real type |
Using std::unique_ptr<Base> to hold Derived | Yes | Destruction still happens through base type |
Virtual destructor vs non-virtual destructor
Cheat Sheet
class Base {
public:
virtual ~Base() = default;
};
Use a virtual destructor when
- the class is a base class for polymorphism
- objects may be deleted through
Base* - you store derived objects in
Base*orstd::unique_ptr<Base>
You usually do not need it when
- the class is final and not used polymorphically
- objects are never deleted through a base pointer
Key rule
Base* p = new Derived();
delete p;
This is safe only if Base has a virtual destructor.
Safe pattern
class Interface {
public:
virtual ~Interface() = default;
virtual void = ;
};
FAQ
Why does a base class need a virtual destructor?
Because deleting an object through a base pointer needs dynamic dispatch to run the correct derived destructor first.
What happens if I delete through a base pointer without a virtual destructor?
The behavior is undefined. Derived cleanup may not run, which can leak resources or cause other bugs.
If a class has virtual methods, should its destructor also be virtual?
Usually yes. That is a common and safe design rule for polymorphic base classes.
Do stack-allocated objects need virtual destructors?
Not just because they are on the stack. The main issue is polymorphic deletion through a base pointer, not stack vs heap.
Do smart pointers make virtual destructors unnecessary?
No. If a smart pointer owns a Derived through a Base type, the base destructor still needs to be virtual.
Can a destructor be pure virtual?
Yes. But it still must have a definition because the base part of the object must always be destroyed.
Does every base class need a virtual destructor?
No. Only base classes intended for polymorphic deletion need one. Some non-polymorphic bases do not.
Mini Project
Description
Build a small C++ program that models a notification system with a polymorphic base class and two derived classes. The goal is to see how virtual destructors ensure correct cleanup when objects are deleted through base-class pointers.
Goal
Create and destroy different notifier objects through std::unique_ptr<Notifier> and verify that both derived and base destructors run in the correct order.
Requirements
- Create a base class named
Notifierwith a virtual destructor and a pure virtualsend()method. - Create at least two derived classes such as
EmailNotifierandSmsNotifier. - Print a message in each destructor so the destruction order is visible.
- Store derived objects in a
std::vector<std::unique_ptr<Notifier>>. - Call
send()on each object and let automatic cleanup happen at the end ofmain().
Keep learning
Related questions
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Casts Explained: C-Style Cast vs static_cast vs dynamic_cast
Learn the difference between C-style casts, static_cast, and dynamic_cast in C++ with clear examples, safety rules, and real usage tips.
C++ Lambda Expressions Explained: What They Are and When to Use Them
Learn what C++ lambda expressions are, why they exist, when to use them, and how they simplify callbacks, algorithms, and local logic.