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
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!
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.