Reputation: 75
I was shocked to notice that the following code is working correctly on my machine, i.e. constructing and destroying all 25 objects of the two-dimensional array in the correct order.
#include <iostream>
class C {
public:
C() { std::cout << '+' << this << "\n"; }
~C() { std::cout << '-' << this << "\n"; }
};
typedef C T[5][5];
int main() {
void *t = new T;
delete (T *) t;
return 0;
}
Is it indeed a valid way of allocating and deallocating multi-dimensional arrays according to the standard? Are operators new
and delete
actually required to introspect multi-dimensional array types recursively in this manner?
Upvotes: 1
Views: 144
Reputation: 72311
This code is not correct, because:
delete
operator does not have the correct type.delete[]
syntax.The correct code would be:
// C and T as in the OP.
int main() {
C (*t)[5] = new T; // or just: auto t = new T;
delete [] t;
}
First, the type of new T
is C (*)[5]
, not T*
(or C*
, etc.) [expr.new]/5:
When the allocated object is an array (that is, the noptr-new-declarator syntax is used or the new-type-id or type-id denotes an array type), the new-expression yields a pointer to the initial element (if any) of the array. [ Note: Both
new int
andnew int[10]
have typeint*
and the type ofnew int[i][10]
isint (*)[10]
— end note ]
The same type must be used in the subexpression of delete[]
, and since the allocated object is an array, the delete[]
operator must be used. [expr.delete]/2:
In a single-object delete expression, the value of the operand of
delete
may be a null pointer value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject representing a base class of such an object. If not, the behavior is undefined. In an array delete expression, the value of the operand ofdelete
may be a null pointer value or a pointer value that resulted from a previous array new-expression. If not, the behavior is undefined. [ Note: This means that the syntax of the delete-expression must match the type of the object allocated bynew
, not the syntax of the new-expression. — end note ]
In particular, if the pointer value resulting from your new-expression was cast to a different type such as T*
, the pointer value of that type is not the pointer value which resulted from the new-expression. Except as noted for deleting a single polymorphic class object, the type of pointer given to delete
or delete[]
needs to be the same as the type of the new-expression.
As far as initializing and destroying goes: No matter how an array object is created, initializing the array object involves initializing each element of that array. When you have an array of arrays, this applies recursively. So the expression new T
creates and initializes one C[5][5]
array object, which means initializing five C[5]
objects, and each of those initializations means initializing five C
objects (for a total of 25 C
constructor calls).
And no matter how an array object was created, a valid way of destroying that array object involves destroying each element of that array, in reverse order. So the delete [] t;
statement destroys the entire array whose first element is *t
- this means destroying all five elements of type C[5]
, and each of those destructions means destroying five C
objects (for a total of 25 ~C
destructor calls).
Note the compiler knows from the type of t
that the elements of the array have type C[5]
, but it doesn't specify the major size of the array. The same expression would be valid if *t
were actually, for example, the first element of a C[8][5]
array instead. Behind the scenes, many compilers will solve this issue by storing a count of array elements next to any array allocated by a new-expression, so that the later delete-expression can determine how many elements need to be destroyed.
Upvotes: 4