当我在普通对象上执行 placement new 时,是否保证保留 object/value 表示?

When I perform placement new on trivial object, Is it guaranteed to preserve the object/value representation?

struct A
{
    int x;
}

A t{};
t.x = 5;

new (&t) A;

// is it always safe to assume that t.x is 5?
assert(t.x == 5);

据我所知,当创建class类型的普通对象时,编译器可以省略显式或隐式默认构造函数的调用,因为不需要初始化。 (是吗?)

那么,如果对一个生命周期已经开始的普通对象执行新的放置,它能保证保留它的object/value表示吗? (如果是这样,我想知道在哪里可以找到规范..)

好吧,让我们征求一些编译器的意见。读一个不确定的值是UB,也就是说如果它出现在一个常量表达式内部,就必须诊断出来。我们不能直接在常量表达式中使用放置 new,但我们 可以 使用 std::construct_at(它具有类型化接口)。我还稍微修改了 class A 以便 value-initialization 与 default-initialization:

做同样的事情
#include <memory>

struct A
{
    int x;
    constexpr A() {}
};

constexpr int foo() {
    A t;
    t.x = 5;
    std::construct_at(&t);
    return t.x;
}

static_assert(foo() == 5);

如你所见on Godbolt,Clang、ICC、MSVC都拒绝该代码,说foo()不是常量表达式。 Clang 和 MSVC 还指出他们在读取 t.x 时遇到问题,他们认为这是对未初始化值的读取。

P0593,虽然与这个问题没有直接关系,但包含一个似乎相关的解释:

The properties ascribed to objects and references throughout this document apply for a given object or reference only during its lifetime.

也就是说,重复使用一个对象占用的存储空间来创建一个新对象总是会破坏旧对象持有的任何,因为一个对象的值随着它的消失而消失寿命。现在,A 类型的对象被其他 A 类型的对象 transparently replaceable,因此即使在其存储空间被重用后,也允许继续使用名称 t。这并不意味着新 t 拥有旧 t 所拥有的价值。这仅表示 t 不是对旧对象的悬空引用。

与 P0593 中所说的相反,GCC 是错误的,而其他编译器是正确的。在常量表达式求值中,需要对这类代码进行诊断。否则就是UB

从标准来看,由于无效使用具有不确定值的对象,程序具有未定义的行为。

Per [basic.life]/8,因为放置 new-expression 创建的类型 A 的对象正好覆盖原始对象 t,在那个点之后使用名称t指的是new-expression.

创建的A对象

[basic.indet]/1中,我们有:

When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced ([expr.ass]).

这里的一个重要细节(我一开始错过了)是“获取存储”不同于“分配存储”或存储区域的存储持续时间。 “获取存储”字样也用于定义对象生命周期的开始 [basic.life]/1 and in the context of a new-expression in [expr.new]/10:

A new-expression may obtain storage for the object by calling an allocation function ([basic.stc.dynamic.allocation]). ... [ Note: ... The set of allocation and deallocation functions that may be called by a new-expression may include functions that do not perform allocation or deallocation; for example, see [new.delete.placement]. — end note ]

因此放置 new-expression 在调用时为 A 类型的对象及其 int 类型的子对象“获取存储” operator new(void*)。为此,存储区域中的内存位置实际上具有静态存储持续时间并没有什么区别。由于创建的具有动态存储持续时间的 int 类型的子对象“未执行初始化”,因此它具有不确定的值。

另见