【问题标题】:Safety of placement new and std::destroy_at in typed std::array?在类型化的 std::array 中放置 new 和 std::destroy_at 的安全性?
【发布时间】:2019-03-20 02:27:12
【问题描述】:

考虑以下示例:

#include <array>
#include <memory>

class trivial
{
public:
    trivial() = default;
    trivial(int a, float b) : m_a(a), m_b(b) {}

private:
    int m_a;
    float m_b;
};

template<typename T>
void write(T& arr, size_t idx, int a, float b)
{
    ::new(static_cast<void*>(std::addressof(arr[idx]))) trivial(a, b);
}

template<typename T>
void destroy(T& arr, size_t idx)
{
    std::destroy_at(std::addressof(arr[idx]));
}

int main()
{
    auto arr = std::array<trivial, 20>();

    write(arr, 3, 10, 20.0f);
    destroy(arr, 3);
}

对任意(但合理的)数据数组使用就地放置newstd::destroy_at 是否安全?这里是否存在任何风险或潜在的未定义行为,或可移植性问题?假设我们不会尝试分配一个被破坏的值,我理解它是未定义的。

我注意到这种方法的基准测试比使用std::aligned_storagereinterpret_cast 更好,主要是因为std::launder 充当了优化阻止程序。如果我对将值存储在 std::array 中的额外限制感到满意(例如需要默认构造函数),这是一个可接受的用例吗?

【问题讨论】:

  • 你有双重毁灭。当arr 超出范围时,它会破坏它的所有元素,包括你在上面调用destroy_at 的那个。我认为它是 UB,但不是 100% 确定(这里的析构函数是微不足道的)。

标签: c++ c++17 destructor memory-alignment placement-new


【解决方案1】:

您对std::array arr 的第三个元素进行了双重破坏。一次通过显式破坏(destroy 调用),另一个通过隐式破坏(当arr 超出范围时)。根据 C++ 标准,这会导致未定义的行为。

15.4 析构函数[class.dtor]
...
16一旦为对象调用析构函数,该对象就不再存在;如果为生命周期已结束的对象调用析构函数,则行为未定义。 [ 示例:如果自动对象的析构函数被显式调用,并且块随后以通常会调用对象的隐式销毁的方式离开,则行为未定义。 ——结束示例]

上面引用中的示例与您尝试做的有点相似。

【讨论】:

  • 还有双重结构。如果交换了写入和销毁的顺序,那么行为将是固定的:在同一个地址上两次构造和销毁。但 sn-p 仍然很危险。
  • 这种双重破坏有什么办法吗?考虑一种情况,比如说,一个哈希集实现,我知道哪些单元格是有效的,哪些不是。我可以在不使用std::aligned_storage(或更具体地说,std::launder)的情况下安全地实现这种就地行为吗?
  • 在这种特殊情况下,当不同存储期限的变量混合在一起时,我认为它不安全。即使是这样,这样的代码也可能无法通过代码审查。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-12
  • 2020-05-21
  • 2021-08-11
  • 2023-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多