4

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?

gsamaras
  • 66,800
  • 33
  • 152
  • 256
CantThinkOfAnything
  • 1,002
  • 13
  • 36

2 Answers2

8

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!

Gábor Angyal
  • 1,947
  • 13
  • 24
  • aah yes, makes sense. Its because of the virtual table that is held with virtual functions. That way c++ knows which destructor to call when the destructor is virtual. When it is not virtual it simply will call the static type destructor. – CantThinkOfAnything Mar 31 '15 at 08:29
  • 3
    Correct, +1. Technically, though, a vtable is only one possible way of implementing virtual calls (albeit by far the most common one). From the point of view of standard C++, only the prescribed behaviour matters. How it's implemented depends solely on the compiler/platform ABI. That's why the standard simply says "the behaviour is undefined" - it's possible that under a different virtual dispatch implementation, something else than "the static type destructor" would happen. – Angew is no longer proud of SO Mar 31 '15 at 08:32
4

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.

Brian Bi
  • 91,815
  • 8
  • 136
  • 249