Code Yarns ‍👨‍💻
Tech BlogPersonal Blog

Always make base class destructor as virtual in C++

📅 2016-Dec-28 ⬩ ✍️ Ashwin Nanjappa ⬩ 📚 Archive

TLDR: The title of this post says it all!

If you are having a class hierarchy, with base class and derived classes, then try to always make the base class destructor as virtual.

I recently noticed an application having a serious memory leak after merging some code. Other than the leak, everything else about the code was executing fine! After debugging the code, the culprit turned out to be a base class destructor that was not virtual. If only the above rule had been followed diligently, the error would have been caught easily.

Why this rule? The reason for this rule is pretty simple. A derived class destructor might be deallocating objects or freeing memory that it had allocated earlier during its creation or execution. Now think about the scenario where this derived class object is held using a base class pointer and it is freed.

Here is a code example that illustrates this scenario:

// Example to show why base class destructor should be virtual.
//
// If ~A is not virtual below, you will notice that B's destructor
// is not called and B's Foo object is not freed. Memory leak!
//
// Change ~A to virtual and Foo is freed at end of scope correctly.

#include <iostream>
#include <memory>
using namespace std;

class Foo
{
    public:
    Foo()  { cout << "Foo ctor\n"; }
    ~Foo() { cout << "Foo dtor\n"; }
};

// Base class
class A
{
    public:
    A()  { cout << "A ctor\n"; }
    // Note: Make this virtual to prevent memory leak
    ~A() { cout << "A dtor\n"; }
};

// Derived class
class B : public A
{
    public:
    B()
    {
        cout << "B ctor\n";
        fp = unique_ptr<Foo>(new Foo);
    }
    ~B() { cout << "B dtor\n"; }
    unique_ptr<Foo> fp;
};

int main()
{
    {
        // Take a base class pointer to derived class object
        unique_ptr<A> a(new B);
    }
    cout << "Scope ended\n";

    return 0;
}

// If ~A is not virtual, output is:
// A ctor
// B ctor
// Foo ctor
// A dtor
// Scope ended

// If ~A is virtual, output is:
// A ctor
// B ctor
// Foo ctor
// B dtor   <-- B destructor called
// Foo dtor <-- Foo object destroyed
// A dtor
// Scope ended