C++23 changes disallow using dynamically allocated array as storage provider?
Asked Answered
B

0

3

This is a basic example of using a dynamic unsigned char array as a "storage" for a type T.

unsigned char* storage = new unsigned char[sizeof(T)];
T* foo = new(storage) T; // line 2
// use *foo somehow
foo->~T(); //destroy our foo

delete[] storage; // Undefined as of C++23? How else to do it?

After line2, the array itself is still alive, because it provides storage to the T, which is nested within the array, but the array elements should now be out-of-lifetime, because their storage has been reused by the T, and T is not nested within them, only within the array itself.

However, how can we delete this array after we're done using it, using the pointer foo, which is a pointer to the out-of-lifetime first element?

Until (exluding) C++23 [basic.life]/6 suggested to allow, or at least didn't forbid, to use this pointer in the delete expression:

The program has undefined behavior if: (6.1) the object will be or was of a class type with a non-trivial destructor and the pointer is used as the operand of a delete-expression,

this does not apply to unsigned char at it's not a class type. However, starting in C++23 this sentence was changed to

The program has undefined behavior if: (6.1) the pointer is used as the operand of a delete-expression,

which now includes unsigned char.

Does this mean it's not supposed to be possible anymore to use a char-array as storage in this way?

My research into the standard has given me 3 possible interpratations so far:

  1. it might have something to do with the "object representation" now being defined to be an array of unsigned chars, meaning that, potentially, the pointer has now come to point to a new unsigned char, part of the "object representation" and is therefore still alive, or

  2. the entire basic.life/6 paragrah is not supposed to apply here given that it says:

and before the storage which the object occupied is reused

which has happened, although this reuse has also ended again by calling ~T(), or

  1. could it even be possible that this was never supposed to work with arrays created with a new-expression considering that the new-expression does not allow us to specify the required alignment anyway?

Which of these, if any, might be correct?

Bryozoan answered 1/9, 2023 at 14:26 Comment(7)
Arguably the bullet you identify doesn't apply, because the paragraph above forbids doing any of the operations "before the storage is released", and delete[] storage; does release the storage, it is not ordered before the release of storage.Daric
You could also, if you like, call placement new new (storage) unsigned char[original_size];, which sets everything back up perfectly for the automatic (trivial) destructor calls which occur during delete[] storage; Now it's "un-reused" in a way that p->~T() does not satisfy.Daric
Do you mean new-placing another unsigned char[] within the existing unsigned char[] just before calling delete[]? Interesting solution. I don't understand your first comment, however. Which operations that are in the snippet are forbidden before the release of the storage? Do you mean line 2 and 3?Bryozoan
Yes to first question (another unsigned char[]), although it isn't "within an existing" one. Then no, I meant that you are interpreting [basic.life]/6.1 to forbid passing the pointer as the operand of delete, but it doesn't forbid that unconditionally. It forbids passing the pointer to delete in a particular interval, starting when the object lifetime has ended and ending when the storage is released. delete[] storage is the end point (release of storage) of that interval. [basic.life]/6 uses the word "before" which indicates that the interval is open and doesn't include its endpoint.Daric
Can you plz github.com/cplusplus/CWG/issues? (Broken by cplusplus.github.io/CWG/issues/2625.html)America
@LanguageLawyer I don't feel confident in my assessment of the interpretations here right now i.e. whether this was ever intended at all, and I don't have a suggested resolution to put in that github issue. I believe you have opened a few issues there before, If you'd like, feel free to open a CWG issue about this, if you believe it's a defect.Bryozoan
@BenVoigt Interesting point about the “before” wording. However, I think if the same logic was applied to basic.life/8 “transparent placement”, it would break, because then the replacement-new would not happen “before” itself.Bryozoan

© 2022 - 2024 — McMap. All rights reserved.