当我在普通对象上执行放置新操作时,是否保证保留对象/值表示?

Eun*_*hoi 7 c++ placement-new

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);
Run Code Online (Sandbox Code Playgroud)

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

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

Bri*_*ian 7

好吧,让我们询问一些编译器的意见。读取不确定值是 UB,这意味着如果它出现在常量表达式内部,则必须对其进行诊断。我们不能直接new在常量表达式中使用放置,但我们可以使用std::construct_at(它有一个类型化接口)。我还稍微修改了该类A,以便值初始化与默认初始化执行相同的操作:

#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);
Run Code Online (Sandbox Code Playgroud)

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

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

本文档中赋予对象和引用的属性仅在给定对象或引用的生命周期内适用。

也就是说,重用对象占用的存储空间来创建新对象总是会破坏旧对象所拥有的任何值,因为对象的值会随着其生命周期而消失。现在, 类型的对象A可以透明地被其他类型的对象替换,因此即使在其存储被重用之后A也允许继续使用该名称。t这并不意味着新的t具有旧的价值t。它仅意味着这t不是对旧对象的悬空引用。

偏离 P0593 中所述,GCC 是错误的,其他编译器是正确的。在常量表达式求值中,需要对此类代码进行诊断。否则就只是UB了。