Pedantically, this may not be OK. As per cppref:
If expression is anything else, including if it is a pointer obtained by the array form of new-expression, the behavior is undefined.
Putting that aside, is the following code OK in practice (T
is non-array, and assuming that new
is not replaced)?
auto p = (T*)operator new(sizeof(T));
new(p) T{};
delete p;
It is said that in cppref that
When calling the allocation function, the new-expression passes the number of bytes requested as the first argument, of type
std::size_t
, which is exactlysizeof(T)
for non-array T.
So I guess this is probably OK. However, it is also said that since C++14,
New-expressions are allowed to elide or combine allocations made through replaceable allocation functions. In case of elision, the storage may be provided by the compiler without making the call to an allocation function (this also permits optimizing out unused new-expression). In case of combining, the allocation made by a new-expression E1 may be extended to provide additional storage for another new-expression E2 if all of the following is true: [...]
Note that this optimization is only permitted when new-expressions are used, not any other methods to call a replaceable allocation function:
delete[] new int[10];
can be optimized out, butoperator delete(operator new(10));
cannot.
I'm not quite sure of the implications. So, is this OK in C++14?
Why am I asking this question? (source)
Sometimes, memory allocation and initialization cannot be done in a single step. You have to manually allocate the memory, do something else, and then initialize the object, e.g., to provide strong exception safety. In this case, if the delete expression cannot be used on the resulting pointer, you have to manually uninitialize and deallocate, which is tedious. To make things worse, in case both new expression and the manual method are employed, you have to track which one is used for each object.
delete
, you have to make sure that your placement new uses suitably-aligned memory which, in this case, does not happen. – Milesoperator new()
guarantees this. This function is required to return a pointer suitably aligned to hold an object of any fundamental alignment. See here. – MikeyT
object using a new-expression. Though a placement one. And thoughp
doesn't point to it because you threw away the result of that expression. – Occurrencenew
. Updated question. – Mikeyp = new(p) T{};
or if the third line weredelete std::launder(p);
? – Sapodillastd::launder()
thing is used to... OK, it's way above my current level to understand XD – Mikeyp
doesn’t point to the newly-constructedT
. The memory address is the same, but the compiler can assume it refers to the (non-existent)T
that was there before the placementnew
. In code this simple I don’t think it would make a difference, but ifT
had aconst
member or something, the compiler might load that from memory before the call tonew
. Thestd::launder
call tells it not to do that kind of optimization, but it’s easier and cleaner to just assign the result of the placementnew
back top
. – Sapodillastd::allocator
; you seem to have independently discovered the need for them but probably do not need to independently implement them yourself. – Sapodillastd::allocator
. But I don't want to drag an allocator object along, or construct a new one just at the place where I need it. So I made these free-function versions :) – Mikey