3

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);
}
Community
  • 1
  • 1
RPFeltz
  • 1,049
  • 2
  • 12
  • 21
  • Arrays don't have destructors, and there's no "placement delete for arrays". You have to remember the allocation size and call all array element destructors manually. That said, this situation cannot really arise in portable code, since [placement array new is unusable](http://stackoverflow.com/q/8720425/596781) in the first place. – Kerrek SB Apr 12 '15 at 18:41
  • Your first block of code is the only standard compliant way of deallocating the memory. – R Sahu Apr 12 '15 at 18:49
  • @Kerrek SB You seem to have misunderstood my question. I'm not talking about placement array new or placement delete for arrays. I'm only using placement new to call the constructor of the class "Neuron", to fill an array. My question is not about any sort of "placement delete", it's about whether or not delete[] will correctly call the destructors of the pointer type of the operand on the elements in the operand if the memory is filled with correct instances of that type, even if it wasn't created as an array of that type. – RPFeltz Apr 12 '15 at 18:51
  • @RPFeltz: Unless the value of the operand of `delete[]` was the result of a previous array-new expression, the behaviour is undefined. `reinterpret_cast(array)` is clearly not the result of a previous array-new expression (but rather the result of the reinterpret-cast expression). – Kerrek SB Apr 12 '15 at 18:56
  • @Kerrek SB You are overlooking that `array` itself was created using `new char[num_elements * sizeof(Object)]`, so it is in fact the result of a previous array-new expression. The cast is there because after creation, I cast the array to `Object*` before storing it in a field. My question is essentially how wide-spread correct behaviour of not doing `delete[] reinterpret_cast(array)`, but rather just `delete[] array` would be in this case, considering that `array` then has a different type (`Object`) than it was created with (`char`). – RPFeltz Apr 12 '15 at 19:04
  • @RPFeltz: I'm aware of that, and the standard could be clearer here, but I think [expr.delete], and in particular the footnote in paragraph 2, make it clear that the value including its type (which is part of the value) has to be the result directly of a matching `new` expression, not some other value derived from it. (In any case, the real-world Itanium ABI would break with your code.) – Kerrek SB Apr 12 '15 at 19:46
  • Can you post the whole code that allocates space and performs placement new? – M.M Apr 13 '15 at 10:33
  • @MattMcNabb: I already have, see the block below the horizontal rule. `neurons(reinterpret_cast*>(new char[num_neurons * sizeof(Neuron)]))` for allocation, then `new(&neuron) Neuron(...)` in the loop. – RPFeltz Apr 13 '15 at 12:09

2 Answers2

2

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.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • To future readers: be very very careful when doing this kind of thing! As mentioned in this answer, there are alignment issues. On any platform you are likely to encounter, `char` will be 1-byte, and so will have 1-byte alignment requirements (i.e. nothing). However, `Object` will almost certainly have stronger alignment requirements; violating this is undefined behaviour and may trap on some architectures, such as SPARC. – jrtc27 Oct 12 '17 at 11:35
  • You're right; thanks. The warning still stands for most other cases though! – jrtc27 Oct 12 '17 at 23:31
1

Here is portable code that manages a dynamic array of objects. It's essentially std::vector:

void * addr = ::operator new(sizeof(Object) * num_elements);
Object * p = static_cast<Object *>(addr);
for (std::size_t i = 0; i != num_elements; ++i)
{
    ::new (p + i) Object(/* some initializer */);
}

// ...

for (std::size_t i = 0; i != num_elements; ++i)
{
    std::size_t ri = num_elements - i - 1;
    (p + ri)->~Object();
}

::operator delete(addr);

This is general pattern how you should organize dynamic storage if you want to have very low-level control. The upshot is that dynamic arrays should never have been a language feature and are much better implemented in library. As I said above, this code is pretty much identical to the existing standard library gadget called std::vector<Object>.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Although you answered my question in the comments by pointing out my code would break on an existing platform, the answer you wrote here doesn't really answer my question clearly. I won't accept it (in hopes you improve it or someone else writes it clearly), but I still gave you an upvote for mentioning the possibility of passing a number argument to `new` instead of creating a char array, since I didn't know about that. – RPFeltz Apr 12 '15 at 20:09