int main()
{
Base *p = new Derived;
delete p;
return 0;
}
I have some confusion, why deleting p here won\'t delete the derived object ? Is
There are many questions in one, and the C++ syntax is unfortunately misleading, so let's review our bases.
What happens in a destructor ?
When an object is destructed the language calls its destructor; the following sequence of events happen, in order:
Note: virtual
bases are destructed before attributes, but after the body is executed.
The important point, here, is that even though a Derived
method hides its Base
counterpart (if any), in the case of constructor and destructor the base counterparts are automatically called for you at a well-defined point over which you have no control.
What of
virtual
destructors ?
When a Base
has a virtual
destructor, the implicitly-declared or user-declared destructor in a Derived
class will naturally override it. Similarly to other virtual
methods it mean that when the destructor is called unqualified (that is, not as in b.Base::foo()
) the call is in fact dispatched to the final-overider, that is the destructor of the most derived object (the real dynamic type of the object).
However, as seen in the previous point, this does not mean that the Base
destructor itself will never run because destructors are special; you could think of the Derived
destructor as being (automatically) implemented as:
Derived::~Derived(): ~Base(), ~attr0(), ~attr1() { ... }
with the code being executed right-to-left.
And what of a
delete
expression on aBase*
?
Well, many people will think that Base* b = ...; delete b;
is desugared as:
// NOT QUITE
Base* b = ...;
b.~Base(); // possibly virtual destructor
operator delete(&b);
However this is in fact incorrect. The issues are that:
Derived
object address might differ from its Base
subobject, and yet operator delete
must be called with the exact pointer value that operator new
returned. operator delete
invoked (which can be overloaded) must be resolved at the definition point of the destructorTherefore, compilers need implement some magic; which is up to them. For example, compilers implementing the Itanium ABI (gcc, icc, clang, ...) will add a special entry to the v-table that contains a magic function that performs the pointer adjustments before calling the destructor of the most derived object and invoke operator delete
with the right address. It could be seen as:
class Derived: public Base {
public:
virtual ~Derived() override {}
// FOR ILLUSTRATION PURPOSE ONLY
// DON'T DO THIS AT HOME:
// - you are forbidden to use `__` in your identifiers
// - you are forbidden to call `delete this;` or any similar statement
// FOR ILLUSTRATION PURPOSE ONLY
virtual void __automagic_delete() {
this->Derived::~Derived();
operator delete(this);
}
};
So, what if
Base::~Base
is notvirtual
?
Well, formally this is undefined behavior.
In practice, two problems arise regularly:
Derived::~Derived
is not called, any resource held by its attributes or another of its base classes are not released. This may cause memory leaks, file descriptor leaks, connector leaks, deadlocks, ...Derived
object has a different address than its Base
subobject, then operator delete
is invoked with an incorrect address... which in stringent implementation immediately leads to an abort
and in less stringent ones might cause memory corruptionsBut of course, since this is undefined behavior, really anything could happen, so this is just the tip of the iceberg.