显式调用析构函数然后使用放置新来重建它是否定义了行为?

Ben*_*Ben 13 c++ lifetime language-lawyer

这非常类似于这与Placement-new 和显式析构函数调用的正确用法,但范围更严格:

如果我有一个类型 ,Sstd::is_nothrow_default_constructible_v<S>std::is_nothrow_destructible_v<S>not std::has_virtual_destructor_v<S>not std::is_polymorphic_v<S>它是调用此函数的定义行为吗?:

template <typename T>
void reconstruct(T& x) noexcept {
    // C++20: requires instead of static_assert:
    static_assert(std::is_nothrow_default_constructible_v<T>);
    static_assert(std::is_nothrow_destructible_v<T>);
    static_assert(!std::has_virtual_destructor_v<T>);
    static_assert(!std::is_polymorphic_v<T>);
    x.~T();
    ::new (&x) T{};
}
Run Code Online (Sandbox Code Playgroud)

如果存在现有的指针或引用怎么办,如

int main() {
    S s;
    s.x = 42;
    const S& sref = s;
    reconstruct(s);
    return sref.x; // Is this UB because the original S sref references no longer exists?
}
Run Code Online (Sandbox Code Playgroud)

我问这个的原因是std::once_flag没有重置机制。我知道为什么它通常不能,而且很容易误用,但是,在某些情况下我想进行线程不安全的重置,并且我认为这种重建模式会给我我想要的,只要这种重建是定义的行为。 https://godbolt.org/z/de5znWdYT

use*_*445 4

不幸的是,有时它是定义的行为,而其他时候您必须通过 运行指针std::launder。在许多情况下,编译器可能会假设对象没有更改,特别是const在 struct 中有引用或字段的情况下S有关存储重用的cppreference 部分提供了更多信息。

S在您展示的特定代码中,如果是完整对象的基类,您将遇到问题,因为编译器可能会在某处缓存到错误的 vtable。

更详细地说,这是 C++17 和 C++20 之间发生的变化。在 C++17 及更早版本中,您需要调用std::launderwhen Scontains any const or reference fields。然而,尽管仍然存在其他限制,但C++20 中已经放宽了该特定要求。