Is there a (semantic) difference between the return value of placement new and the casted value of its operand?
Only a
can safely be used to directly access the Foo
object created by the placement new-expression (which we'll call x
for ease of reference). Using b
requires std::launder
.
The value of a
is specified in [expr.new]/1:
If the entity is a non-array object, the result of the new-expression
is a pointer to the object created.
The value of a
is therefore "pointer to x
". This pointer, of course, can safely be used to access x
.
reinterpret_cast<Foo*>(buffer)
applies the array-to-pointer conversion to buffer
(see [expr.reinterpret.cast]/1). The resulting value after the conversion is applied is "pointer to the first element of buffer
".
This is a reinterpret_cast
of an object pointer to an object pointer of a different type, and is defined as equivalent to static_cast<Foo*>(static_cast<void*>(buffer))
by [expr.reinterpret.cast]/7.
The inner cast to void*
is actually an implicit conversion. Per [conv.ptr]/2,
The pointer value is unchanged by this conversion.
Therefore the inner cast yields a void*
with the value "pointer to the first element of buffer
".
The outer cast is governed by [expr.static.cast]/13, which I've lightly reformatted into bullet points:
A prvalue of type “pointer to cv1
void
” can be converted to a prvalue of type “pointer to cv2T
”, whereT
is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1.
If the original pointer value represents the address
A
of a byte in memory andA
does not satisfy the alignment requirement ofT
,
then the resulting pointer value is unspecified.Otherwise, if the original pointer value points to an object
a
, and there is an objectb
of typeT
(ignoring cv-qualification)
that is pointer-interconvertible witha
, the result is a pointer to
b
.Otherwise, the pointer value is unchanged by the conversion.
Assuming that buffer
is suitably aligned (you'd be in trouble well before this point if it's not), the first bullet is inapplicable. The second bullet is likewise inapplicable as there's no pointer-interconvertiblity here. It follows that we hit the third bullet - "the pointer value is unchanged by the conversion" and remains "pointer to the first element of buffer
".
Thus, b
does not point to the Foo
object x
; it points instead to the first char
element of buffer
, even though its type is Foo*
. It therefore cannot be used to access x
; attempting to do so yields undefined behavior (for the non-static data member case, by omission from [expr.ref]; for the non-static member function case, by [class.mfct.non-static]/2).
To recover a pointer to x
from b
, std::launder
can be used:
b = std::launder(b); // value of b is now "pointer to x"
// and can be used to access x
Is it OK to discard placement new return value when initializing objects
Ignoring the return value is not OK both pedantically and practically.
From a pedantic point of view
For p = new(p) T{...}
, p
qualifies as a pointer to an object created by a new-expression, which does not hold for new(p) T{...}
, despite the fact that the value is the same. In the latter case, it only qualifies as pointer to an allocated storage.
The non-allocating global allocation function returns its argument with no side effect implied, but a new-expression (placement or not) always returns a pointer to the object it creates, even if it happens to use that allocation function.
Per cppref's description about the delete-expression (emphasis mine):
For the first (non-array) form, expression must be a pointer to a object type or a class type contextually implicitly convertible to such pointer, and its value must be either null or pointer to a non-array object created by a new-expression, or a pointer to a base subobject of a non-array object created by a new-expression. If expression is anything else, including if it is a pointer obtained by the array form of new-expression, the behavior is undefined.
Failing to p = new(p) T{...}
therefore makes delete p
undefined behavior.
From a practical point of view
Technically, without p = new(p) T{...}
, p
does not point to the newly-initialized T
, despite the fact that the value (memory address) is the same. The compiler may therefore assume that p
still refers to the T
that was there before the placement new. Consider the code
p = new(p) T{...} // (1)
...
new(p) T{...} // (2)
Even after (2)
, the compiler may assume that p
still refers to the old value initialized at (1)
, and make incorrect optimizations thereby. For example, if T
had a const member, the compiler might cache its value at (1)
and still use it even after (2)
.
p = new(p) T{...}
effectively prohibits this assumption. Another way is to use std::launder()
, but it is easier and cleaner to just assign the return value of placement new back to p
.
Something you may do to avoid the pitfall
template <typename T, typename... Us>
void init(T*& p, Us&&... us) {
p = new(p) T(std::forward<Us>(us)...);
}
template <typename T, typename... Us>
void list_init(T*& p, Us&&... us) {
p = new(p) T{std::forward<Us>(us)...};
}
These function templates always set the pointer internally. With std::is_aggregate
available since C++17, the solution can be improved by automatically choosing between ()
and {}
syntax based on whether T
is an aggregate type.
Placement new and assignment of class with const member
There is nothing that makes the shown code snippet inherently UB. However, it is almost certain UB will follow immediately under any normal usage.
From [basic.life]/8 (emphasis mine)
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
the storage for the new object exactly overlays the storage location which the original object occupied, and
the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
the original object was a most derived object of type
T
and the new object is a most derived object of typeT
(that is, they are not base class subobjects).
Since there is a const
member in s
, using the original variable after a call to operator=
will be UB.
s var{42};
var = s{420}; // OK
do_something(var.id); // UB! Reuses s through original name
do_something(std::launder(&var)->id); // OK, this is what launder is used for
Does using non-array placement new for neighboring elements create an array?
Am I right? Can I refer to this memory as an array? If not, can this functionality be achieved somehow?
This is basically the same underlying problem that std::vector
has with data()
and pointed out in CWG 2182. It's undefined behavior to refer to this memory as an array, but practically speaking it needs to work because so much code relies on it working. There's no way to achieve it.
Side-node, don't call your callables lambda
. They don't have to be lambdas. And also let the language help you with pointer offsetting:
auto p = static_cast<Object*>(storage);
for (int i = 0; i < size; ++i) {
construct(static_cast<void*>(p + i), i);
}
std::launder and strict aliasing rule
The strict aliasing rule is a restriction on the type of the glvalue actually used to access an object. All that matters for the purpose of that rule are a) the actual type of the object, and b) the type of the glvalue used for the access.
The intermediate casts the pointer travels through are irrelevant, as long as they preserve the pointer value. (This goes both ways; no amount of clever casts - or laundering, for that matter - will cure a strict aliasing violation.)
f
is valid as long as ptr
actually points to an object of type int
, assuming that it accesses that object via int_ptr
without further casting.
example_1
is valid as written; the reinterpret_cast
s do not change the pointer value.
example_2
is invalid because it gives f
a pointer that doesn't actually point to an int
object (it points to the out-of-lifetime first element of the storage
array). See Is there a (semantic) difference between the return value of placement new and the casted value of its operand?
static_cast'd pointer value
You may misunderstand the term "pointer value". The term is defined in [basic.compound]/3:
Every value of pointer type is one of the following:
a pointer to an object or function (the pointer is said to point to the object or function), or
a pointer past the end of an object ([expr.add]), or
the null pointer value ([conv.ptr]) for that type, or
an invalid pointer value.
A value of a pointer type that is a pointer to or past the end of an object represents the address of the first byte in memory ([intro.memory]) occupied by the object or the first byte in memory after the end of the storage occupied by the object, respectively.
So you can see the term "pointer value" in the standard is a very abstract term. Even if two pointer values represent the same address, they may have different values. The example in cppreference demonstrates the concept of "pointer value" nicely:
struct S1 { int a; } s1;
struct S2 { int a; private: int b; } s2; // not standard-layout
union U { int a; double b; } u = {0};
int arr[2];
int* p1 = reinterpret_cast<int*>(&s1); // value of p1 is "pointer to s1.a" because s1.a
// and s1 are pointer-interconvertible
int* p2 = reinterpret_cast<int*>(&s2); // value of p2 is unchanged by reinterpret_cast and
// is "pointer to s2".
int* p3 = reinterpret_cast<int*>(&u); // value of p3 is "pointer to u.a": u.a and u are
// pointer-interconvertible
double* p4 = reinterpret_cast<double*>(p3); // value of p4 is "pointer to u.b": u.a and u.b
// are pointer-interconvertible because both
// are pointer-interconvertible with u
int* p5 = reinterpret_cast<int*>(&arr); // value of p5 is unchanged by reinterpret_cast and
// is "pointer to arr"
Related Topics
Why Are Redundant Scope Qualifications Supported by the Compiler, and Is It Legal
How to Cout a Float Number with N Decimal Places
Which Boost Features Overlap with C++11
How to Get Screenshot of a Window as Bitmap Object in C++
Removing a Non Empty Directory Programmatically in C or C++
Use Wm_Copydata to Send Data Between Processes
Is the Use of Std::Vector<Bool> Objects in C++ Acceptable, or Should I Use an Alternative
Istream Behavior Change in C++ Upon Failure
Dereferencing an Invalid Pointer, Then Taking the Address of the Result
Explain Morris Inorder Tree Traversal Without Using Stacks or Recursion
Does Boost Have a Datatype for Set Operations That Is Simpler Than the Stl
Lambda Implicit Capture Fails with Variable Declared from Structured Binding
Std::Vector, Default Construction, C++11 and Breaking Changes
What Is a Glibc Free/Malloc/Realloc Invalid Next Size/Invalid Pointer Error and How to Fix It