When virtual destructors are needed in C++: analysis in 2025

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Virtual destructors in C++: why this is still relevant (and when you can forget about it)

\n\n

If you've ever written in C++ and encountered inheritance, you've probably seen this question on Stack Overflow with tens of thousands of views: «When to use virtual destructors?». In 2025, the topic hasn't lost its urgency — memory leaks due to a missing virtual continue to take their toll in production. But there is also a flip side: mindlessly adding virtual everywhere possible is also not a good practice.

\n\n

Let's figure it out once and for all: when a virtual destructor is mandatory, when it's just good practice, and when its presence is a sign of poor architecture. Let's go.

\n\n

Short answer (for those in a hurry)

\n\n

A virtual destructor is needed if and only if you are going to delete an object of a derived class through a pointer to the base class. If there is no such scenario in the code, virtual is not required. But there are nuances that we will discuss below.

\n\n

Why is this even a problem? Anatomy of a leak

\n\n

Let's look at a classic antipattern that breaks the code of beginners (and not only).

\n\n
#include <iostream>\n\nclass Base {\npublic:\n    ~Base() { \n        std::cout << "Base destructor\\n"; \n    }\n};\n\nclass Derived : public Base {\nprivate:\n    int* big_data = new int[1000]; // resources that need to be freed\npublic:\n    Derived() { std::cout << "Derived created\\n"; }\n    ~Derived() {\n        delete[] big_data;\n        std::cout << "Derived destructor\\n";\n    }\n};\n\nint main() {\n    Base* ptr = new Derived();\n    delete ptr;  // Oops! Only ~Base() will be called\n    return 0;\n}
\n\n

Output:

\n
Derived created\nBase destructor
\n\n

The big_data array leaked. The Derived destructor was not called because the compiler, seeing the type Base*, called the destructor of that particular class. And it is not virtual.

\n\n

The solution is to add virtual before the Base destructor:

\n\n
class Base {\npublic:\n    virtual ~Base() = default;  // or empty body\n};
\n\n

Now, with delete ptr, ~Derived() will be executed first, then ~Base(). Resources are freed, memory is not leaking, the static analyzer is happy.

\n\n

When a virtual destructor is mandatory (ironclad rule)

\n\n

There is only one unconditional case: if the class is intended for polymorphic use through a pointer or reference to the base class, and objects of derived classes will be deleted through this pointer.

\n\n

In practice, this means:

\n
    \n
  • The class has at least one virtual function (pure or not)
  • \n
  • You plan to use Base* or Base& to work with Derived
  • \n
  • Objects are created on the heap (new) and deleted via delete
  • \n
\n\n

Example — a classic shape hierarchy:

\n\n
class Shape {\npublic:\n    virtual double area() const = 0;\n    virtual ~Shape() = default;  // mandatory for an abstract class\n};\n\nclass Circle : public Shape {\n    double radius;\npublic:\n    explicit Circle(double r) : radius(r) {}\n    double area() const override { return 3.14159 * radius * radius; }\n};\n\nint main() {\n    std::vector<std::unique_ptr<Shape>> shapes;\n    shapes.push_back(std::make_unique<Circle>(5.0));\n    // unique_ptr will take care of delete itself, but the Shape destructor must be virtual\n    return 0;\n}
\n\n

Note: even if you use std::unique_ptr (which is the recommended approach in 2025), the virtual destructor is still needed. unique_ptr calls delete through a pointer to the base class —

Blogs

Book Recommendations