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
Deletion of an object via a pointer to its base class is undefined unless the base has a virtual destructor. Even with a virtual destructor, the order of deletion may not be what immediately comes to mind.
For more information, see this destructor reference.
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.
If you are asking about the behavior in situation when base class has no virtual destructor, then your confusion is stemming for the fact hat you already have a number of pre-conceived misconceptions about the behavior of this delete
expression
"It will only be able to delete the base class part of the object"
"This will cause memory leaks"
None of this makes any sense. There's no memory leaks here, and there's nothing as deterministic as being able to "delete the base class part" of it.
If the base class has no virtual destructor the behavior of such code is simply undefined. There are quite a few different ways that undefined behavior can manifest itself, including but not limited to improper destructor call, improper operator delete
selection, heap corruption and, yes, "memory leaks", both direct and indirect. There are many different things that can get screwed up in this case. Not just some "memory leaks", as the popular misconception makes people believe. (Where does that popular bit about "memory leaks" come from. Does anyone know?)
So, you do need virtual destructor here. And the full list of reasons you need it can get quite long, if one decides to make a full effort to analyze it exhaustively. But in any case, this is an implementation detail. There's no specific explanation of what will really happen without tying it to a specific implementation.
As for the "conceptual" explanation... There's always the most obvious one: it is, of course, necessary to invoke the proper destructor in order to performs the proper destruction. Even if we simply limit out consideration to user-defined destruction steps (i.e. what the user explicitly wrote in their derived-class destructor), we still need destructor polymorphism to properly invoke that destructor.
However, there are also a number of other internal reasons. For example, in a typical implementation selection of the proper operator delete
for raw memory deallocation also piggybacks on the virtuality of the destructor (see here, for example)
You should declare a virtual destructor:
class Base {
//etc...
virtual ~Base();
};
class Derived {
//etc...
virtual ~Derived();
};
Base* p = new Derived();
delete p;
(Of course many things are missing above, including constructors)