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:
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
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
- could it even be possible that this was never supposed to work with arrays created with a
new
-expression considering that thenew
-expression does not allow us to specify the required alignment anyway?
Which of these, if any, might be correct?
delete[] storage;
does release the storage, it is not ordered before the release of storage. – Daricnew (storage) unsigned char[original_size];
, which sets everything back up perfectly for the automatic (trivial) destructor calls which occur duringdelete[] storage;
Now it's "un-reused" in a way thatp->~T()
does not satisfy. – Daricunsigned char[]
within the existingunsigned char[]
just before callingdelete[]
? 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? – Bryozoanunsigned 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