Question

When to Use Virtual Destructors in C++

cpppolymorphismshared-ptrvirtual-destructor

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:

  1. Derived::~Derived() runs
  2. 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++:

"If an object is destroyed through a base-class pointer or reference, use the actual runtime type and run the correct destructor chain."

So if a class is meant to be used polymorphically, its destructor should usually be virtual.

A common rule is:

  • If a class has any virtual function, it should usually have a virtual destructor.
  • More precisely: if a class is intended to be deleted through a base pointer, the destructor must be virtual.

Example:

class Base {
public:
    virtual ~Base() = default;
};

class Derived : public Base {
public:
    ~Derived() {
        // cleanup specific to Derived
    }
};

Now this is safe:

Base* ptr = new Derived();
delete ptr;

C++ will call:

  1. Derived::~Derived()
  2. Base::~Base()

This is exactly what you want.

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 Derived object

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
Take Quiz

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

That means the derived cleanup did not happen.

Correct version

#include <iostream>
using namespace std;

class Base {
public:
    virtual ~Base() {
        cout << "Base destructor\n";
    }
};

class Derived : public Base {
public:
    ~Derived() override {
        cout << "Derived destructor\n";
    }
};

int main() {
    Base* ptr = new Derived();
    delete ptr;
}

Now the output is:

Derived destructor
Base destructor

Important note about override

Using override on a destructor is valid and helpful:

~Derived() override = default;

It lets the compiler confirm that the base destructor is virtual and that you are correctly overriding it.

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:

  1. Animal* pet = new Dog();

    • Memory is allocated for a Dog object.
    • The pointer type is Animal*, but the actual object is Dog.
  2. delete pet;

    • C++ sees that pet points to a polymorphic base type.
    • Because Animal has a virtual destructor, C++ performs dynamic dispatch.
    • It checks the real runtime type of the object.
  3. Dog::~Dog() runs first.

    • This cleans up the Dog part of the object.
  4. Animal::~Animal() runs next.

    • This cleans up the base part.
  5. The memory is released.

If Animal did not have a virtual destructor, step 2 would not safely dispatch to Dog::~Dog(), and behavior would be undefined.

Direct object destruction is different

Dog d;

When d goes out of scope, C++ already knows the object is a Dog. No base pointer is involved, so destruction works correctly even if you are not deleting through a base pointer.

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:

class FileLogger : public Logger {
    // may close file in destructor
};

If destroyed via Logger*, the base destructor must be virtual.

4. Smart pointers to base types

std::unique_ptr<Base> obj = std::make_unique<Derived>();

When the smart pointer destroys the object, the same rule applies: the base destructor must be virtual if deletion happens through the base type.

Take Quiz

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.

Smart pointer usage

Modern C++ code often avoids manual new and delete, but the rule still matters:

std::unique_ptr<Base> ptr = std::make_unique<Derived>();

When ptr is destroyed, it deletes through Base*, so Base needs a virtual destructor.

Guarding against misuse

Some classes are intentionally not meant to be deleted through a base pointer. In that case, developers may:

  • avoid virtual functions entirely
  • make the destructor protected
  • document ownership rules clearly

Example:

class Base {
protected:
    ~Base() = default;
};

This prevents code like delete basePtr; from outside the class hierarchy.

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

delete does two things:

  1. runs the destructor
  2. releases the memory

If the wrong destructor runs, resource cleanup may be skipped even if memory is freed.

4. Forgetting that smart pointers do not remove the rule

This still depends on a virtual destructor:

std::unique_ptr<Base> ptr = std::make_unique<Derived>();

Smart pointers automate deletion, but they still delete through the base type.

5. Adding virtual to every destructor without understanding why

Not every class needs a virtual destructor.

Usually you need one when:

  • the class is a base class
  • the class is used polymorphically
  • objects may be deleted through a base pointer

A simple concrete class with no inheritance role usually does not need a virtual destructor.

6. Confusing object slicing with deletion issues

Object slicing is a different problem:

Derived d;
Base b = d; // slices Derived part

That is not the same as deleting through a base pointer, though both involve base/derived relationships.

Comparisons

SituationVirtual destructor needed?Why
Class is never used as a base classUsually noNo polymorphic deletion is expected
Base class with virtual functionsUsually yesStrong sign that objects will be handled polymorphically
Object deleted through Base*YesRequired for correct derived destruction
Object created and destroyed directly as DerivedNo for this specific caseC++ already knows the real type
Using std::unique_ptr<Base> to hold DerivedYesDestruction still happens through base type

Virtual destructor vs non-virtual destructor

FeatureNon-virtual destructorVirtual destructor
Dynamic dispatch on deleteNoYes
Safe to delete derived object via base pointerNoYes
Typical useConcrete non-polymorphic classesBase classes used polymorphically

Virtual destructor vs pure virtual destructor

A destructor can also be pure virtual:

class Base {
public:
    virtual ~Base() = 0;
};

Base::~Base() {}

Comparison:

TypeMeaning
virtual ~Base() = default;Virtual destructor with normal implementation
virtual ~Base() = 0;Makes class abstract, but destructor still needs a definition
Take Quiz

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* or std::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 run() = 0;
};

Common modern style

~Derived() override = default;

Important edge case

A pure virtual destructor still needs a definition:

class Base {
public:
    virtual ~Base() = 0;
};

Base::~Base() {}

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.

Take Quiz

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

  1. Create a base class named Notifier with a virtual destructor and a pure virtual send() method.
  2. Create at least two derived classes such as EmailNotifier and SmsNotifier.
  3. Print a message in each destructor so the destruction order is visible.
  4. Store derived objects in a std::vector<std::unique_ptr<Notifier>>.
  5. Call send() on each object and let automatic cleanup happen at the end of main().
Take Quiz

Keep learning

Related questions

View more