8

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.

  • 2
    You are assuming a destructor only exists if written manually; this assumption is faulty: the language provides a ~derived() that delegates to vec's destructor. Alternatively, you are assuming that unique_ptr<base> pt would know the derived destructor. Without a virtual method, this cannot be the case. While a unique_ptr may be given a deletion function that is a template parameter without any runtime representation, and that feature is of no use for this code. – amon Dec 30 '18 at 21:53
  • Can we put braces on the same line to make code shorter? Now I have to scroll. – laike9m Jan 5 at 12:22
9

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 the object that _ptr points to. 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.

0

Inheritance

The whole point of inheritance is to share a common interface and protocol among many different implementations such that an instance of a derived class can be treated identically to any other instance from any other derived type.

In C++ inheritance also brings with it implementation details, marking (or not marking) the destructor as virtual is one such implementation detail.

Function Binding

Now when a function, or any of its special cases like a constructor or destructor is called, the compiler must choose which function implementation was meant. Then it must generate machine code that follows this intention.

The simplest way to work this would be to select the function at compile time and emit just enough machine code so that regardless of any values, when that piece of code executes, it always runs the code for the function. This works great except for inheritance.

If we have a base class with a function (could be any function, including the constructor or destructor) and your code calls a function on it, what does this mean?

Taking from your example, if you called initialize_vector() the compiler has to decide if you really meant to call the implementation found in Base, or the implementation found in Derived. There are two ways to decide this:

  1. The first is to decide that because you called from a Base type, you meant the implementation in Base.
  2. The second is to decide that because the runtime type of the value stored in the Base typed value could be Base, or Derived that the decision as to which call to make, must be made at runtime when called (each time it is called).

The compiler at this point is confused, both options are equally valid. This is when virtual comes into the mix. When this keyword is present the compiler picks option 2 delaying the decision between all the possible implementations till the code is running with a real value. When this keyword is absent the compiler picks option 1 because that is the otherwise normal behaviour.

The compiler might still pick option 1 in the case of a virtual function call. But only if it can prove that this is always the case.

Constructors and Destructors

So why don't we specify a virtual Constructor?

More intuitively how would the compiler pick between identical implementations of the constructor for Derived and Derived2? This is pretty simple, it can't. There is no pre-existing value from which the compiler can learn what was really intended. There is no pre-exisiting value because that is the job of the constructor.

So why do we need to specify a virtual destructor?

More intuitively how would the compiler choose between implementations for Base and Derived? They are just function calls, so the function call behaviour happens. Without a declared virtual destructor the compiler will decide to bind directly to the Base destructor regardless of the values runtime type.

In many compilers, if the derived does not declare any data members, nor inherit from other types, the behaviour in the ~Base() will be suitable, but it is not guaranteed. It would work purely by happenstance, much like standing in front of a flamethrower that had not yet been ignited. You are fine for a while.

The only correct way to declare any base or interface type in C++ is to declare a virtual destructor, so that the correct destructor is called for any given instance of that type's type hierarchy. This allows the function with the most knowledge of the instance to clean that instance up correctly.

Your Answer

By clicking "Post Your Answer", you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.