C++ Inheritance – Why Base Class Needs a Virtual Destructor

allocationcinheritancememory

The following code causes a memory leak:

#include <iostream>
#include <memory>
#include <vector>

using namespace std;

class base
{
    void virtual initialize_vector() = 0;
};

class derived : public base
{
private:
    vector<int> vec;

public:
    derived()
    {
        initialize_vector();
    }

    void initialize_vector()
    {
        for (int i = 0; i < 1000000; i++)
        {
            vec.push_back(i);
        }
    }
};

int main()
{
    for (int i = 0; i < 100000; i++)
    {
        unique_ptr<base> pt = make_unique<derived>();
    }
}

It didn't make much sense to me, since class derived allocates no raw dynamic memory, and unique_ptr deallocates itself. I get that class base's implicit destructor is being called instead of derived's, but I don't get why that's a problem here. If I were to write an explicit destructor for derived, I wouldn't write anything for vec.

Best Answer

When the compiler goes to execute the implicit delete _ptr; inside of the unique_ptr's destructor (where _ptr is the pointer stored in the unique_ptr), it knows precisely two things:

  1. The address of the object to be deleted.
  2. The type of pointer which _ptr is. Since the pointer is in unique_ptr<base>, that means _ptr is of the type base*.

This is all the compiler knows. So, given that it is deleting an object of type base, it will invoke ~base().

So... where's the part where it destroys the dervied object that it actually points to? Because if the compiler doesn't know that it is destroying a derived, then it doesn't know derived::vec exists at all, let alone that it should be destroyed. So you've broken the object by leaving half of it undestroyed.

The compiler cannot assume that any base* being destroyed is actually a derived*; after all, there could be any number of classes derived from base. How would it know which type this particular base* actually points to?

What the compiler has to do is figure out the correct destructor to call (yes, derived has a destructor. Unless you = delete a destructor, every class has a destructor, whether you write one or not). To do this, it will have to use some information stored in base to obtain the right address of the destructor code to invoke, information that is set by the constructor of the actual class. Then it has to use this information to convert the base* to a pointer to the address of the corresponding derived class (which may or may not be at a different address. Yes, really). And then it can invoke that destructor.

That mechanism I just described? It is commonly called "virtual dispatch": aka, that thing that happens any time you call a function marked virtual when you have a pointer/reference to a base class.

If you want to call a derived class function when all you have is a base class pointer/reference, that function must be declared virtual. Destructors are fundamentally no different in this regard.