virtual destructor in c++
Asked Answered
R

4

-7

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;
}
Remodel answered 24/10, 2014 at 12:15 Comment(4)
Would you prefer it wasn't?Gaillardia
I don't follow the question. Are you asking why deleting a Derived object should call the Derived destructor? Because that's how a Derived object is supposed to be destroyed. Or are you asking about the mechanism behind it, so that it works correctly with a Base pointer? It's exactly the same as any other virtual function call.Leola
I just want to know the mechanism why the drived destructor is invoke ?Remodel
@Sajid: The mechanism is "that's what C++ does and it's a good thing too".Wingo
W
3

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.

Whom answered 24/10, 2014 at 12:24 Comment(1)
Otherwise, the behavior is undefined. In any number of cases, it will crash the machine, for example.Vapid
E
1

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.

Ethbin answered 24/10, 2014 at 12:52 Comment(2)
vtables are an implementation detail.Wingo
@LightnessRacesinOrbit Yes... "I just want to know the mechanism" - I've given an indicative illustration of the universally used implementation mechanism, though certainly others are possible.Ethbin
S
0

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.

Shulman answered 24/10, 2014 at 12:25 Comment(0)
E
0

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.

Etrem answered 24/10, 2014 at 12:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.