Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Virtual destructor in polymorphic classes

I understand that whenever you have a polymorphic base class, the base class should define a virtual destructor. So that when a base-class pointer to a derived-class object is deleted, it will call the destructor of the derived class first. Correct me if i am wrong here.

also, if the base-class destructor were to be non-virtual, it would be undefined behavior to delete a baseclass pointer to a derived object. Correct me if i am wrong aswell.

so my question is: Why is it exactly, that when the base-class destructor is non-virtual, the object will not be destroyed correctly?

i am assuming this is because virtual functions have some kind of table that is memorized and consulted whenever a virtual function is called. And the compiler knows that when an object is supposed to be deleted, it should call the derived destructor first.

is my assumption correct?

like image 450
CantThinkOfAnything Avatar asked Dec 14 '22 15:12

CantThinkOfAnything


2 Answers

If at the point where you delete the object the static type of the variable is the bas type, than the destructor of the base type will be called, but the destructor of the sub class won't be called (as it is not virtual).

As a result the resources allocated by the base class will be freed, but the resources allocated by the sub class won't.

Thus the object won't be destructed correctly.

You are correct about that table: it is called a virtual method table or "vtable". But the result of the destructor being non-virtual is not that the destructors are not called in the correct order, but that the destructor(s) of the sub class(es) are not called at all!

like image 124
Gábor Angyal Avatar answered Jan 01 '23 04:01

Gábor Angyal


Consider

struct Base {
    void f() { printf("Base::f"); }
};
struct Derived : Base {
    void f() { printf("Derived::f"); }
};
Base* p = new Derived;
p->f();

This prints Base::f, because Base::f is not virtual. Now do the same with destructors:

struct Base {
    ~Base() { printf("Base::~Base"); }
};
struct Derived : Base {
    ~Derived() { printf("Derived::~Derived"); }
};
Base* p = new Derived;
p->~Base();

This prints Base::~Base. Now if we make the destructor virtual, then, as with any other virtual function, the final overrider in the dynamic type of the object is called. A destructor overrides a virtual destructor in a base class (even though its "name" is different):

struct Base {
    virtual ~Base() { printf("Base::~Base"); }
};
struct Derived : Base {
    ~Derived() override { printf("Derived::~Derived"); }
};
Base* p = new Derived;
p->~Base();

The call p->~Base() actually invokes Derived::~Derived(). Since this is a destructor, after its body finishes executing, it automatically invokes destructors of bases and members. So the output is

Derived::~Derived
Base::~Base

Now, a delete-expression is in general equivalent to a destructor call followed by a call to a memory deallocation function. In this particular case, the expression

delete p;

is equivalent to

p->~Base();
::operator delete(p);

So if the destructor is virtual, this does the right thing: it calls Derived::~Derived first, which then automatically calls Base::~Base when it's done. If the destructor isn't virtual, the likely result is that only Base::~Base is invoked.

like image 29
Brian Bi Avatar answered Jan 01 '23 04:01

Brian Bi