【问题标题】:C++ proper way to move element of aligned_storage arrayC++ 移动对齐存储数组元素的正确方法
【发布时间】:2019-06-10 11:49:02
【问题描述】:

正如标题所说,我想知道在数组中移动元素的正确方法是什么:

std::array<std::aligned_storage_t<sizeof(T), alignof(T)>, N> data;

就这么简单吗:

data[dst] = data[src];

或者我是否需要添加其他内容,例如移动,因为它的存储未初始化,我是否需要使用复制或移动构造函数,例如:

new (&data[dst]) T(std::move(data[src]));

由于 data[src] 不是正确的类型 T,我是否需要改为:

new (&data[dst]) T(std::move(*std::launder(reinterpret_cast<T*>(&data[src])));

我正在寻找最灵活的方式来移动任何 T 可能的项目,包括仅移动类型等。

基本上,我正在创建一个压缩数组,它始终将元素移动到内存中连续的位置,即使删除元素以防止数组的活动部分出现漏洞。

编辑: 由于 cmets 想要一个最小的例子,我猜是这样的:

template<class T, std::size_t N>
class SimpleExampleClass {
    std::array<std::aligned_storage_t<sizeof(T), alignof(T)>, N> data;

public:
    void move_element(std::size_t src, std::size_t dst) {
        // data[dst] = data[src]; ?
        // or
        // new (&data[dst]) T(std::move(data[src]));
        // or
        // new (&data[dst]) T(std::move(*std::launder(reinterpret_cast<T*>(&data[src])));
        // or
        // something else?

        // then I would need some way to clean up the src element, not sure what would suffice for that.
        // as calling a destructor on it could break something that was moved potentially?
    }

    // Other functions to manipulate the data here... (example below)
    template<typename ...Args>
    void emplace_push(Args&&... args) noexcept {
        new (&data[/*some index*/]) T(std::forward<Args>(args)...);
    }

    void push(T item) noexcept {
        emplace_push(std::move(item));
    }
};

【问题讨论】:

  • data[dst] 中有什么内容? data[src] 中有什么内容?出示您的minimal reproducible example
  • @Evg 所以我会使用new (&amp;data[dst]) T(std::move(*std::launder(reinterpret_cast&lt;T*&gt;(&amp;data[src]))); 之类的东西,对吧?
  • 提示:使用aligned_storage 时,您需要知道您已经构建了哪些项目(好吧,如果std::is_pod&lt;T&gt; 为假)。所以你以后只能破坏它们。
  • @KamilCuk, BTW, std::is_pod 在 C++20 中已弃用。

标签: c++ move-semantics placement-new


【解决方案1】:

std::aligned_storage 本身,粗略地说,只是一个字节的集合。没有什么可动的,std::move(data[src]) 只是一个空操作。您应该首先使用placement new 创建一个对象,然后您可以通过在新位置移动构造它来移动该对象。

简单示例:

auto ptr = new (&data[0]) T();
new (&data[1]) T(std::move(*ptr));
std::destroy_at(ptr);

如果T 类似于unique_ptr,或任何其他类似的边缘情况,正确调用旧元素索引上的销毁应该没有任何问题?

从一个对象移出会使其处于某种有效状态,并且该对象仍然必须被销毁。

由于data[0] 只是字节的集合,指向它的指针会起作用,还是需要重新解释该指针才能在移动构造函数中使用?

如果它装饰有reinterpret_caststd::launder,它将起作用,就像您在问题中所写的那样:

new (&data[1]) T(std::move(*std::launder(reinterpret_cast<T*>(&data[0]))));

标准库包含一些用于处理未初始化内存的有用函数。完整列表可在here 找到(请参阅未初始化的存储部分)。

【讨论】:

  • 好的,这基本上就是我对new (&amp;data[dst]) T(std::move(*std::launder(reinterpret_cast&lt;T*&gt;(&amp;data[src]))); 的想法,也就是说,在 T 类似于 unique_ptr 或任何其他类似边缘情况的情况下,不应该有任何问题在旧元素索引上调用销毁正确吗?
  • @HexCrown,当你从std::unique_ptr 移动时,对象仍然存在,你仍然需要销毁它。
  • 好的。另外,由于 data[0] 只是一个字节的集合,指向它的指针会起作用,还是需要重新解释该指针才能在移动构造函数中使用?
  • @HexCrown,正如您所写,您可以将该指针与reinterpret_caststd::launder 一起使用。
  • 非常感谢,只是想确保我的想法是正确的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-13
相关资源
最近更新 更多