std::construct_at当我阅读cppref示例代码时,我注意到std::destroy_at在未初始化的缓冲区上调用了它:

alignas(S) unsigned char storage[sizeof(S)]{};
S uninitialized = std::bit_cast<S>(storage);
std::destroy_at(&uninitialized);

我不知怎的认为这应该是 UB 操作,因为对象的生命周期尚未开始。std::destroy_at根据 C++ 标准,调用未初始化的缓冲区是有效操作吗?

4

  • 3
    注意将启动生命周期uninitialized(因此 S 的构造函数已被调用)


    – 


  • 2
    它不是在缓冲区上调用它,而是在结果对象上调用它std::bit_cast– 任何潜在的 UB 都会首先出现在那里。如果结果不是未定义的,那么我们会在对象的生命周期内调用 destroy_at。


    – 


  • 2
    归根结底S x = ...; std::destroy_at(&x);,这bit_cast只是一个转移注意力的话题。这本身并不是 UB,但x如果对象已经死亡,那么在作用域末尾的自动析构函数调用就是 UB,我相信。


    – 

  • 不清楚为什么这个例子有storage——它的唯一目的似乎是在位转换中潜在地导致 UB。最好从构造S开始std::destroy_at()


    – 



最佳答案
1

std::destroy_at调用未构造的对象是错误的——那将是 UB。

但这不是这里发生的事情。它是S在 处的对象上调用的uninitialized,而不是 上的storage。后者似乎是个转移注意力的花招——没有充分的理由uninitialized从随机字节构造而不是简单地对其进行聚合初始化。

一个更清楚的例子将忽略干扰storage变量:

S uninitialized{0, 0.0f, 0.0};
std::destroy_at(&uninitialized);

示例中确实存在一个错误:我们有

std::destroy_at(ptr);

ptr指向uninitialized。因此在范围的末尾,我们对同一个对象进行了第二次析构,即 UB。

如果删除该行,该示例将变为有效。

7

  • 即使我们像这样写S uninitialized{0, 0.0f, 0.0}; std::destroy_at(&uninitialized);,在范围末尾析构函数unintialized也会被再次调用,这是 UB?不是吗?


    – 

  • 是的,如果我们不std::construct_at()替换它的话,那就对了。这就是我在答案的后半部分描述的错误。


    – 

  • 2
    我认为这是一个非常糟糕的示例代码,可能应该从头开始重写。


    – 

  • @TobySpeight“如果我们不 std::construct_at() 代替它” – 正是这样做的(它只是没有在问题中显示),但随后它destroy_at再次调用,因此问题仍然存在


    – 

  • 3
    如果程序以静态、线程或自动存储持续时间结束类型 T 的对象的生命周期,并且如果 T 具有非平凡的析构函数,则程序必须确保在隐式析构函数调用发生时原始类型的对象占据相同的存储位置;否则程序的行为是未定义的。——S因此,在退出块之前无需“复活”可平凡破坏的变量;如果具有非平凡的析构函数,这只会是 UB 。


    –