虚析构函数的机制
从StackoverFlow上面找到的一个比较满意的答案:
I will type an example :
class A { public: virtual ~A(){} }; class B: public A { public: ~B() { } }; int main(void) { A * a = new B; delete a; return 0; }
Now in Above Example , destructors will be called recursively bottom to up . My Question is how Compiler do this MAGIC .
Good Answer:
There are two different pieces of magic in your question. The first one is how does the compiler call the final overrider for the destructor and the second one is how does it then call all the other destructors in order.
Disclaimer: The standard does not mandate any particular way of performing this operations, it only mandates what the behavior of the operations at a higher level are. These are implementation details that are common to various implementations, but not mandated by the standard.
How does the compiler dispatch to the final overrider?
The first answer is the simple one, the same dynamic dispatch mechanism that is used for other virtual
functions is used for destructors. To refresh it, each object stores a pointer (vptr
) to each of its vtable
s (in the event of multiple inheritance there can be more than one), when the compiler sees a call to any virtual function, it follows the vptr
of the static type of the pointer to find the vtable
and then uses the pointer in that table to forward the call. In most cases the call can be directly dispatched, in others (multiple inheritance) it calls some intermediate code (thunk) that fixes the this
pointer to refer to the type of the final overrider for that function.
How does the compiler then call the base destructors?
The process of destructing an object takes more operations than those you write inside the body of the destructor. When the compiler generates the code for the destructor, it adds extra code both before and after the user defined code.
Before the first line of a user defined destructor is called, the compiler injects code that will make the type of the object be that of the destructor being called. That is, right before ~derived
is entered, the compiler adds code that will modify the vptr
to refer to the vtable
of derived
, so that effectively, the runtime type of the object becomes derived
(*).
After the last line of your user defined code, the compiler injects calls to the member destructors as well as base destructor(s). This is performed disabling dynamic dispatch, which means that it will no longer come all the way down to the just executed destructor. It is the equivalent of adding this->~mybase();
for each base of the object (in reverse order of declaration of the bases) at the end of the destructor.
With virtual inheritance, things get a bit more complex, but overall they follow this pattern.
EDIT (forgot the (*)): (*) The standard mandates in §12/3:
When a virtual function is called directly or indirectly from a constructor (including from the mem-initializer for a data member) or from a destructor, and the object to which the call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructor’s own class or in one of its bases, but not a function overriding it in a class derived from the con- structor or destructor’s class, or overriding it in one of the other base classes of the most derived object.
That requirement implies that the runtime type of the object is that of the class being constructed/destructed at this time, even if the original object that is being constructed/destructed is of a derived type. A simple test to verify this implementation can be:
struct base { virtual ~base() { f(); } virtual void f() { std::cout << "base"; } }; struct derived : base { void f() { std::cout << "derived"; } }; int main() { base * p = new derived; delete p; }