Does dynamic_cast work inside overloaded operator delete?
I came across this:
struct Base {
void* operator new (size_t);
void operator delete (void*);
virtual ~Base () {} // <--- polymorphic
};
struct Der开发者_StackOverflow社区ived : Base {};
void Base::operator delete (void *p)
{
Base *pB = static_cast<Base*>(p);
if(dynamic_cast<Derived*>(pB) != 0)
{ /* ... NOT reaching here ? ... */ }
free(p);
}
Now if we do,
Base *p = new Derived;
delete p;
Surprisingly, the condition inside the Base::delete is not satisfied
Am I doing anything wrong ? Or casting from void*
looses the information of Derived*
?
Function operator delete
is a raw-memory deallocation function. It is invoked when the actual object (that used to reside in that memory) has already been destructed. I.e. by the time you get into operator delete
you object has already been wiped out. The memory the pointer points to is essentially "raw", it no longer contains an object. Trying to use any polymorphic functionality on this raw memory is useless - it will not work.
In more formal terms, according to the language standard the lifetime of an object with non-trivial destructor ends once its destructor starts. In your case all destructors have already done their work. The lifetime of the object is already over, while dynamic_cast
requires a "live" object.
P.S. Formally, it is permissible to use dynamic_cast
in a destructor as long as some conditions are met (see 12.7/5), but when all destructors are finished (as in your case), dynamic_cast
is no longer usable.
Once your operator delete
overload gets the pointer, the pointed-to object has been destroyed (the ~Derived()
destructor has already been called).
You can't treat it like a Base
or Derived
object at all anymore after it is destroyed because it isn't a Base
or Derived
object anymore.
As already mentioned by the other two answers, the type of an object changes as the destructors are being executed. Once a destructor completes, the object of that type does no longer exist, and only it's base subobjects exists (until their destructors complete).
The reason for this answer is proposing an interesting experiment, what will the output of this code be? (Oh, well, all three answers already told you, but the experiment is interesting in itself):
#include <iostream>
struct base {
static void print_type( base const & b ) { // [1]
std::cout << b.type() << std::endl;
}
virtual std::string type() const { // [2]
return "base";
}
virtual ~base() { print_type( *this ); }
base() { print_type( *this ); }
};
struct derived : base {
std::string type() const {
return "derived";
}
~derived() { print_type( *this ); }
derived() { print_type( *this ); }
};
struct most_derived : derived {
std::string type() const {
return "most_derived";
}
~most_derived() { print_type( *this ); }
most_derived() { print_type( *this ); }
};
int main() {
most_derived md;
base::print_type( md );
}
Notes:
For extra fun, calls to print_type
are also added in the constructor. The function serves as verification of the dynamic type of the object at that particular point in time. The function print_type
(that could be a freestanding function, and implemented in a different translation unit --as to avoid the compiler from seeing inside it). While compiling the function, the compiler cannot know whether it is called from inside a constructor, destructor, or outside of any of them, so the generated code must use the dynamic dispatch mechanism, and will be dispatched to the final overrider at each point in time.
As to the validity of the code is guaranteed by §12.7/2:
To explicitly or implicitly convert a pointer (an lvalue) referring to an object of class X to a pointer (reference) to a direct or indirect base class B of X, the construction of X and the construction of all of its direct or indirect bases that directly or indirectly derive from B shall have started and the destruction of these classes shall not have completed, otherwise the conversion results in undefined behavior. To form a pointer to (or access the value of) a direct nonstatic member of an object obj, the construction of obj shall have started and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing the member value) results in undefined behavior.
Conversions to base&
on the call to print_type
are valid as they are performed after the construction of each object has started, and before the destruction of each object has completed (each refers to each one of the subobjects of most_derived
in the program).
精彩评论