In the code below, why is the ~Derived()
destructor called automatically?
#include<iostream>
using namespace std;
class Base
{
public:
virtual ~Base()
{
cout << "Calling ~Base()" << endl;
}
};
class Derived: public Base
{
private:
int* m_pnArray;
public:
Derived(int nLength)
{
m_pnArray = new int[nLength];
}
virtual ~Derived()
{
cout << "Calling ~Derived()" << endl;
delete[] m_pnArray;
}
};
int main()
{
Derived *pDerived = new Derived(5);
Base *pBase = pDerived;
delete pBase;
return 0;
}
Because your base class destructor is virtual
virtual ~Base();
the call to delete on a pointer to a base class results in virtual call to destructor and as any virtual call is dispatched to matching function in derived class. It is not only good, but necessary: otherwise the behavior is undefined.
This is crucial for a derived classes which destructor is not an empty function. Non-virtual call would otherwise result in calling base class destructor, derived resources being leaked, etc.
When you have at least one virtual
function in a class, then the compiler creates a single table for the class listing the member function pointers. Consider:
struct Base
{
virtual ~Base() { };
int n_;
};
In pseudo-code you can imagine the compiler adding:
void* Base::__virtual_dispatch_table[] = { (void*)&Base::~Base };
Then, when you have an actual object of type Base
it will have an extra hidden data member that points to the Base::__virtual_dispatch_table
(the "VDT"):
Variable definition Memory layout
------------------- -------------
Base myBase; int n_;
void** __p_vdt = Base::__virtual_dispatch_table;
Now, if you have a Base* p
and delete p;
, the compiler says "hey - it's virtual
- I won't hardcode a call to Base::~Base
, instead I'll generate code that does something like this pseudo-code:
void (Base::*p_destructor) = p->__p_vdt[0]
*p_destructor(p); // "p" will provide the "this" value while the destructor runs
Why would you want to do all that? Because when you come along with a Derived
object...
class Derived: public Base
{
private:
int* m_pnArray;
...
...the compiler can create a separate virtual dispatch table...
void* Derived::__virtual_dispatch_table[] = { (void*)&Derived::~Derived };
...andd lay out the Derived object's memory like this:
Variable definition Memory layout
------------------- -------------
Derived derived; int n_;
void** __p_vdt = Derived::__virtual_dispatch_table;
int* m_pnArray;
Notice that the __p_vdt
is in the same relative location within the object layout, but now points to the Derived
class's virtual dispatch table?
Now, if you create a Base*
to derived
, the exact same code needed to call the destructor for a Base
object, which - in case you've lost track - was...
void (Base::*p_destructor) = p->__p_vdt[0]
*p_destructor(p); // "p" will provide the "this" value while the destructor runs
...can be run but will end up using the Derived
object's __p_vdt
value of Derived::__virtual_dispatch_table
, and finding the Derived
class's destructor.
Because it allows you to treat any Base
object (which may in fact be a Derived
) as an object that you can delete.
In this case, if delete pBase
didn't call the Derived
destructor, the data held by m_pnArray
would never get deleted, i.e. a "memory leak" would occur.
When you call
delete pBase;
It looks at the virtual function table of pBase to find the appropriate destructor to begin unwinding at, and it finds Derived::~Derived and then works its way down the stack.