DEV Community

Tanishk Gupta
Tanishk Gupta

Posted on

The Silent Memory Leak: Why Every C++ Base Class Needs a Virtual Destructor

Deleting a derived object through a base class pointer can silently cause memory leaks in C++.

Yes — even if your program runs without errors.

Let’s understand why this happens and how virtual destructors fix it.

If you ever worked with inheritance in C++, you might have used the upcasting. If not don't worry, we will discuss this.

What is Upcasting?

Pointing to a derived (child) object using a base class pointer. It’s a powerful feature of polymorphism.

// this is upcasting
BaseClass* b = new ChildClass()
Enter fullscreen mode Exit fullscreen mode

However, if the base class destructor is not declared as virtual, deleting the object through a base class pointer can cause a memory leak — especially if the derived class allocates memory on the heap.

The Problem

When we delete an object using a base class pointer, C++ decides which destructor to call based on the pointer type — not the actual object type.

This behavior is called static binding.

Example Without Virtual Destructor

#include <iostream>
using namespace std;

class Base {
public:
    Base() { cout << "Base Constructor\n"; }

    ~Base() { cout << "Base Destructor\n"; }
};

class Derived : public Base {
    int* data; 
public:
    Derived() { 
        cout << "Derived Constructor\n"; 
        data = new int[100]; // Memory allocated on heap
    }

    ~Derived() { 
        cout << "Derived Destructor (Memory freed!)\n"; 
        delete[] data; 
    }
};

int main() {
    cout << "--- Creating Object ---\n";
    Base* ptr = new Derived(); // Upcasting

    cout << "\n--- Deleting Object ---\n";
    delete ptr; 

    return 0;
}
Enter fullscreen mode Exit fullscreen mode

Output

Output

What happens here?

  • Only the Base destructor is called
  • The Derived destructor is not called
  • The dynamically allocated memory (data) is never freed

This leads to a memory leak.

Fix: Using Virtual Destructor

class Base {
public:
    Base() { cout << "Base Constructor\n"; }

    virtual ~Base() { cout << "Base Destructor\n"; }
};

/*Rest of the code same...*/
Enter fullscreen mode Exit fullscreen mode

Output

Output

What happens now?

  • The Derived destructor is called first
  • Then the Base destructor is called
  • Memory allocated in the derived class is properly freed

By using virtual, we tell the compiler to determine the correct destructor at runtime based on the actual object type.

This is called dynamic binding.

Final Takeaway

If you delete objects using base class pointers, a virtual destructor is not optional — it is essential.

Ignoring this can lead to subtle memory leaks that are hard to detect in large applications.

Happy Coding!

Top comments (0)