In order to use placement new instead of automatically attempting to call the default constructor, I'm allocating an array using reinterpret_cast<Object*>(new char[num_elements * sizeof(Object)])
instead of new Object[num_elements]
.
However, I'm not sure how I should be deleting the array so that the destructors get called correctly. Should I loop through the elements, call the destructor manually for each element, and then cast the array to a char*
and use delete[]
on that, like this:
for (size_t i = 0; i < num_elements; ++i) {
array[i].~Object();
}
delete[] reinterpret_cast<char*>(array);
Or is it sufficient if I don't call the destructor manually for each element, and simply rely on delete[] to do that since the type of the array is Object*
, like delete[] array
?
What I'm worried about, is that not every platform might be able to determine the amount of elements in the array correctly that way, because I didn't allocate the array using a type of the right size. An answer to a question about "how delete[] knows the size of the operand" suggests that a possible implementation of delete[]
would be to store the number of allocated elements (rather than the amount of bytes).
If delete[]
is indeed implemented that way, that would suggest that using just delete[] array
would try to delete too many elements, because the array was created with more char
elements than how many Object
elements fit in it. So in that case, the only reliable way to delete the array would be to manually call the destructors, cast the array to a char*
, and then use delete[]
.
However, another logical way to implement it would be to store the size of the array in bytes, rather than the amount of elements, and then when calling delete[]
, divide the size of the array by the size of the type to get the amount of elements to call the destructor of. If this method is used, then just using delete[] array
where array
has a type of Object*
would be sufficient.
So my question is: can I rely on delete[] to correctly call the destructors of the elements in the operand array, if the array was originally not allocated with the right type?
This is the code I'm using:
template <typename NumberType>
NeuronLayer<NumberType>::NeuronLayer(size_t num_inputs, size_t num_neurons, const NumberType *weights)
: neurons(reinterpret_cast<Neuron<NumberType>*>(new char[num_neurons * sizeof(Neuron<NumberType>)])),
num_neurons(num_neurons), num_weights(0) {
for (size_t i = 0; i < num_neurons; ++i) {
Neuron<NumberType> &neuron = neurons[i];
new(&neuron) Neuron<NumberType>(num_inputs, weights + num_weights);
num_weights += neuron.GetNumWeights();
}
}
and
template <typename NumberType>
NeuronLayer<NumberType>::~NeuronLayer() {
delete[] neurons;
}
or
template <typename NumberType>
NeuronLayer<NumberType>::~NeuronLayer() {
for (size_t i = 0; i < num_neurons; ++i) {
neurons[i].~Neuron();
}
delete[] reinterpret_cast<char*>(neurons);
}
Calling delete[]
on an Object*
will call the destructor once for every object allocated by new[]
. new Object[N]
typically stores N before the actual array, and delete[]
certainly knows where to look.
Your code doesn't store that count. And it can't, since it's an unspecified implementation detail where and how the count is stored. As you speculate, there are two obvious ways: element count and array size, and one obvious location (before the array). Even so, there could be alignment issues, and you can't predict what type is used for the size.
Also, new unsigned char[N]
is a special case since delete[]
doesn't need to call destructors of char
. In that case new[]
doesn't need to store N
at all. So you can't even bank on that size being stored, even if new Object[N]
would have stored a size.