0
votes

In the code below, b is a base-class pointer. However, when I invoke the destructor (either explicitly or implicitly via delete), the derived class destructor is invoked first. I don't understand how this works. There could be any number of derived classes, each with their own destructors. How can the compiler know which derived class destructor to invoke from the base destructor?

#include <iostream>

using namespace std;

class Base {
public:
    virtual ~Base() { cout << "Base destructor" << endl; }
};

class Derived : public Base {
public:
    ~Derived() { cout << "Derived destructor" << endl; }
};

int main(int argc, char * argv[]) {

    Base * b = new Derived();
    b->~Base(); // delete b; has the same result
}
5
Same way as normal virtual functions.chris
Oh, I see. I didn't realize that virtual functions of derived classes could be invoked from base class pointers. I see that destructors are merely a special case.master_latch

5 Answers

1
votes

dynamic binding, the compiler doesn't decide, the runtime does because the destructor is virtual. C++ destruction calls the destructor on the current class and implicitly calls the parent class until it hits the base class.

1
votes

The call to virtual destructor works the same as a call to any other virtual function, as a result of virtual dispatch via virtual table. Apart from this,

b->~Base(); // delete b; "has the same result"

this is not true, because delete also frees the memory, which you haven't done here. delete b calls a destructor for *b and deallocates raw memory to operating system. You have only destroyed the building but haven't given ground back.

1
votes

This is done the same way as virtual functions. Its called dynamic binding. When non virtual member functions are resolved statically means at compile-time, virtual members are resolved dynamically means during run time. Compiler maintains a vtable for this. If the object has one or more virtual functions, the compiler puts a hidden pointer in the object called a "virtual-pointer" or "v-pointer." This v-pointer points to a global table called the "virtual-table" or "v-table.". Read more in details from here.

1
votes

It doesn't. It happens the other way around. Normal virtual function despatching calls the derived destructor, and the derived destructor calls the base destructor.

0
votes

Note: My first answer was so off-base that I deleted it. It was so far off-base that someone should have down voted my response. This is another try.

In the opening post, master_latch asked:

How can the compiler know which derived class destructor to invoke from the base destructor?

How this happens is implementation specific.

Why this has to happen is "because the standard says so." Here's what the standard says:

C++11 12.4 paragraph 5:

After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct non-variant members, the destructors for X’s direct base classes and, if X is the type of the most derived class, its destructor calls the destructors for X’s virtual base classes. All destructors are called as if they were referenced with a qualified name, that is, ignoring any possible virtual overriding destructors in more derived classes. Bases and members are destroyed in the reverse order of the completion of their constructor. A return statement in a destructor might not directly return to the caller; before transferring control to the caller, the destructors for the members and bases are called. Destructors for elements of an array are called in reverse order of their construction.

C++11 12.4 paragraph 10:

In an explicit destructor call, the destructor name appears as a ~ followed by a type-name or decltype-specifier that denotes the destructor’s class type. The invocation of a destructor is subject to the usual rules for member functions, ...

The example code in C++11 12.4 paragraph 10 indicates the intent of the above:

struct B {
  virtual ~B() { }
};
struct D : B {
  ~D() { }
};

D D_object;
B* B_ptr = &D_object;

void f() {
  D_object.B::~B();   // calls B’s destructor
  B_ptr->~B();        // calls D’s destructor
  ...
}



master_latch, your example of using b->~Base(); is identical to the second call in the example code. Think of b->~Base(); as if it meant b->__destruct_me(). It is in a sense no different than a call to any other virtual function.

A compliant implementation has to do this because "because the standard says so." How an implementation does it? The standard doesn't say. (That's good requirementese, by the way. Say what has to be done, but don't say how to do it.)

Most implementations (every implementation I have poked into) do this by generating multiple functions for a destructor. One function implements the body of the destructor as specified by the programmer. A wrapper destructor executes this body of the destructor function, then destroys non-static data members in reverse order of construction, and then invokes parent class destructors. That classes can virtually inherit from some parent class adds another twist. This means that a third destructor function for a given class might be needed.

So how does an implementation know that b->~Base() should call the wrapper destructor for class Derived? Dynamically casting a pointer to a polymorphic class to a void* pointer yields a pointer to the most derived object.

C++11 5.2.7 paragraph 7:

If T is “pointer to cv void,” then the result is a pointer to the most derived object pointed to by v. Otherwise, a run-time check is applied to see if the object pointed or referred to by v can be converted to the type pointed or referred to by T.

In other words, dynamically casting a polymorphic pointer to void* yields a pointer to the object as it was declared or allocated. The virtual table (not a part of the standard) dictates how to find the destructor. The implementation ensures that the pointer to the virtual table can be determined from a void* pointer to the most derived object. This is what lets the implementation know which destructor to call. From that point on, the pointer to that most derived object is no longer a void* pointer.