C++ 移动 aligned_storage 数组元素的正确方法
C++ proper way to move element of aligned_storage array
正如标题所说,我想知道在数组中移动元素的正确方法是什么,例如:
std::array<std::aligned_storage_t<sizeof(T), alignof(T)>, N> data;
是不是就这么简单:
data[dst] = data[src];
或者我是否需要添加其他东西,比如移动,因为它的存储是未初始化的,我是否需要使用复制或移动构造函数,比如:
new (&data[dst]) T(std::move(data[src]));
由于数据[src] 不是正确的类型 T,我是否需要改为:
new (&data[dst]) T(std::move(*std::launder(reinterpret_cast<T*>(&data[src])));
我正在寻找最灵活的方式来移动任何 T 可能的项目,包括仅移动类型等。
基本上我正在创建一个压缩数组,它始终将元素移动到内存中连续,即使为了防止数组的活动部分出现空洞而删除了元素。
编辑:
由于评论想要一个最小的例子,我猜是这样的:
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));
}
};
std::aligned_storage
粗略地说,它本身只是一个字节的集合。没有什么可移动的,std::move(data[src])
只是一个no-op。您应该首先使用 placement new 创建一个对象,然后您可以将该对象 move-constructing 移动到新位置。
简单示例:
auto ptr = new (&data[0]) T();
new (&data[1]) T(std::move(*ptr));
std::destroy_at(ptr);
in the case of T
being something like unique_ptr
, or any other similar edge case, there shouldn't be any issue with calling the destroy on the old element index correct?
从一个对象移动会使它处于某种有效状态,并且该对象仍然必须被销毁。
since data[0]
is just a collection of bytes, would a pointer to it work, or would that pointer need to be reinterpret cast before being used in the move constructor?
如果它装饰有 reinterpret_cast
和 std::launder
,它将起作用,就像您在问题中写的那样:
new (&data[1]) T(std::move(*std::launder(reinterpret_cast<T*>(&data[0]))));
标准库包含一些用于处理未初始化内存的有用函数。可以在 here 中找到完整列表(请参阅 未初始化存储 部分)。
正如标题所说,我想知道在数组中移动元素的正确方法是什么,例如:
std::array<std::aligned_storage_t<sizeof(T), alignof(T)>, N> data;
是不是就这么简单:
data[dst] = data[src];
或者我是否需要添加其他东西,比如移动,因为它的存储是未初始化的,我是否需要使用复制或移动构造函数,比如:
new (&data[dst]) T(std::move(data[src]));
由于数据[src] 不是正确的类型 T,我是否需要改为:
new (&data[dst]) T(std::move(*std::launder(reinterpret_cast<T*>(&data[src])));
我正在寻找最灵活的方式来移动任何 T 可能的项目,包括仅移动类型等。
基本上我正在创建一个压缩数组,它始终将元素移动到内存中连续,即使为了防止数组的活动部分出现空洞而删除了元素。
编辑: 由于评论想要一个最小的例子,我猜是这样的:
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));
}
};
std::aligned_storage
粗略地说,它本身只是一个字节的集合。没有什么可移动的,std::move(data[src])
只是一个no-op。您应该首先使用 placement new 创建一个对象,然后您可以将该对象 move-constructing 移动到新位置。
简单示例:
auto ptr = new (&data[0]) T();
new (&data[1]) T(std::move(*ptr));
std::destroy_at(ptr);
in the case of
T
being something likeunique_ptr
, or any other similar edge case, there shouldn't be any issue with calling the destroy on the old element index correct?
从一个对象移动会使它处于某种有效状态,并且该对象仍然必须被销毁。
since
data[0]
is just a collection of bytes, would a pointer to it work, or would that pointer need to be reinterpret cast before being used in the move constructor?
如果它装饰有 reinterpret_cast
和 std::launder
,它将起作用,就像您在问题中写的那样:
new (&data[1]) T(std::move(*std::launder(reinterpret_cast<T*>(&data[0]))));
标准库包含一些用于处理未初始化内存的有用函数。可以在 here 中找到完整列表(请参阅 未初始化存储 部分)。