在类型化的std :: array中放置new和std :: destroy_at的安全性?

时间:2019-03-20 02:27:13

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

考虑以下示例:

#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中的其他限制(例如需要默认构造函数)感到满意,这是可以接受的用例吗?

1 个答案:

答案 0 :(得分:3)

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

  

15.4析构函数[class.dtor]
  ...
  16一旦为某个对象调用了析构函数,该对象就不再存在;如果为生存期已结束的对象调用析构函数,则行为未定义。 [示例:如果显式调用了自动对象的析构函数,并且随后以通常会隐式破坏对象的方式保留该块,则该行为是不确定的。 —完示例]

以上引用中的示例与您尝试执行的操作有些相似。