I found the following snippet in the C++03 Standard under 5.3.5 [expr.delete] p3
:
In the first alternative (delete object), if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the operand’s dynamic type and the static type shall have a virtual destructor or the behavior is undefined. In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.
Quick review on static and dynamic types:
struct B{ virtual ~B(){} };
struct D : B{};
B* p = new D();
Static type of p
is B*
, while the dynamic type of *p
is D
, 1.3.7 [defns.dynamic.type]
:
[Example: if a pointer
p
whose static type is “pointer toclass B
” is pointing to an object ofclass D
, derived fromB
, the dynamic type of the expression*p
is “D
.”]
Now, looking at the quote at the top again, this would mean that the follwing code invokes undefined behaviour if I got that right, regardless of the presence of a virtual
destructor:
struct B{ virtual ~B(){} };
struct D : B{};
B* p = new D[20];
delete [] p; // undefined behaviour here
Did I misunderstand the wording in the standard somehow? Did I overlook something? Why does the standard specify this as undefined behaviour?
Base* p = new Base[n]
creates ann
-sized array ofBase
elements, of whichp
then points to the first element.Base* p = new Derived[n]
however, creates ann
-sized array ofDerived
elements.p
then points to theBase
subobject of the first element.p
does not however refer to the first element of the array, which is what a validdelete[] p
expression requires.Of course it would be possible to mandate (and then implement) that
delete [] p
Does The Right Thing™ in this case. But what would it take? An implementation would have to take care to somehow retrieve the element type of the array, and then morallydynamic_cast
p
to this type. Then it's a matter of doing a plaindelete[]
like we already do.The problem with that is that this would be needed every time an array of polymorphic element type, regardless of whether the polymorphism is used on not. In my opinion, this doesn't fit with the C++ philosophy of not paying for what you don't use. But worse: a polymorphic-enabled
delete[] p
is simply useless becausep
is almost useless in your question.p
is a pointer to a subobject of an element and no more; it's otherwise completely unrelated to the array. You certainly can't dop[i]
(fori > 0
) with it. So it's not unreasonable thatdelete[] p
doesn't work.To sum up:
arrays already have plenty of legitimate uses. By not allowing arrays to behave polymorphically (either as a whole or only for
delete[]
) this means that arrays with a polymorphic element type are not penalized for those legitimate uses, which is in line with the philosophy of C++.if on the other hand an array with polymorphic behaviour is needed, it's possible to implement one in terms of what we have already.
Just to add to the excellent answer of sth - I have written a short example to illustrate this issue with different offsets.
Note that if you comment out the m_c member of the Derived class, the delete operation will work well.
Cheers,
Guy.
IMHO this has to do with limitation of arrays to deal with constructor/destructor. Note that, when
new[]
is called, compiler forces to instantiate only default constructor. In the same way whendelete[]
is called, compiler might look for only the destructor of calling pointer's static type.Now in the case of
virtual
destructor, Derived class destructor should be called first followed by the Base class. Since for arrays compiler might see the static type of calling object (here Base) type, it might end up calling just Base destructor; which is UB.Having said that, it's not necessarily UB for all compilers; say for example gcc calls destructor in proper order.
I think it all comes down to the zero-overhead principle. i.e. the language doesn't allow storing information about the dynamic type of elements of the array.
It's wrong to treat an array-of-derived as an array-of-base, not only when deleting items. For example even just accessing the elements will usually cause disaster:
b[5]
will use the size ofB
to calculate which memory location to access, and ifB
andD
have different sizes, this will not lead to the intended results.Just like a
std::vector<D>
can't be converted to astd::vector<B>
, a pointer toD[]
shouldn't be convertible to aB*
, but for historic reasons it compiles anyway. If astd::vector
would be used instead, it would produce a compile time error.This is also explained in the C++ FAQ Lite answer on this topic.
So
delete
causes undefined behavior in this case because it's already wrong to treat an array in this way, even though the type system can't catch the error.