问题
Is it defined behavior to placement-new a trivially destructible base object of a derived?
struct base { int& ref; };
struct derived : public base {
complicated_object complicated;
derived(int& r, complicated_arg arg) :
base {r}, complicated(arg) {}
};
unique_ptr<derived> rebind_ref(unique_ptr<derived>&& ptr,
int& ref) {
// Change where the `ref` in the `base` subobject of
// derived refers.
return unique_ptr<derived>(static_cast<derived*>(
::new (static_cast<base*>(ptr.release()) base{ref}));
}
Note that I tried to structure rebind_ref
to not break any strict aliasing assumptions a compiler might have made.
回答1:
No, this is not allowed by the C++ Standard, for at least two reasons.
The text that sometimes allows a placement-new of a new object into the storage for another object of the same type is found in [basic.life], paragraph 8. The bold emphasis is 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
[C++17] 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).[C++20 draft 2018-10-09] neither the original object nor the new object is a potentially-overlapping subobject ([intro.object]).
The C++20 change is to account for the possibility of zero-size non-static data members, but it still also rules out all base class subobjects (empty or not). "Potentially-overlapping subobject" is a new term defined in [intro.object] paragraph 7:
A potentially-overlapping subobject is either:
a base class subobject, or
a non-static data member declared with the
no_unique_address
attribute ([dcl.attr.nouniqueaddr]).
(Even if you do find some way to rearrange things to avoid the reference member and base class issues, remember to make sure nobody can ever define a const derived
variable, for example by making all constructors private!)
回答2:
static_cast<derived*>(
::new (&something) base{ref})
is not valid, by definition new (...) base(...)
creates a base
object as a new complete object, that can be considered an existing complete object or member subobject sometimes (under conditions that aren't met by base
anyway) but never a base subobject.
There is no existing rule that says you can pretend new (addr) base
creates a valid derived object just because the base
object is overwriting another base
base subobject. If there previously was a derived
object, you have just reused its storage with new (addr) base
. Even if by some magic the derived
object was still in existence, the result of the evaluation of the new expression wouldn't point to it, it would point to a base
complete object.
If you want to pretend you did something (like creating a derived
object), without actually doing it (calling a derived
constructor), you can pour some volatile
qualifiers on pointers to force the compiler to erase all assumptions on values and compile the code as if there was an ABI transition.
来源:https://stackoverflow.com/questions/52884257/placement-new-base-subobject-of-derived-in-c