Reputation: 763
So as the title says, I'm wondering what the proper way to move an element in an array such as:
std::array<std::aligned_storage_t<sizeof(T), alignof(T)>, N> data;
Is it as simple as doing:
data[dst] = data[src];
Or do I need to add something else like a move, being that its storage is uninitialized, do I need to use the copy or move constructors, something like:
new (&data[dst]) T(std::move(data[src]));
Since the data[src] is not the proper type T, do i need to instead do:
new (&data[dst]) T(std::move(*std::launder(reinterpret_cast<T*>(&data[src])));
I'm looking for the most flexible way of moving the item for anything T might be, including move only types etc.
Basically I'm creating a packed array that always moves elements to be contiguous in memory, even when ones are removed to prevent holes in the active section of the array.
Edit: As the comments want a minimal example, I guess something like:
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));
}
};
Upvotes: 3
Views: 648
Reputation: 26342
std::aligned_storage
itself is, roughly speaking, just a collection of bytes. There is nothing to move, and std::move(data[src])
is just a no-op. You should first use placement new to create an object and then you can move that object by move-constructing it at the new location.
Simple example:
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?
Moving from an object leaves it in some valid state, and the object still has to be destroyed.
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?
It will work if it is adorned with reinterpret_cast
and std::launder
, like you wrote in your question:
new (&data[1]) T(std::move(*std::launder(reinterpret_cast<T*>(&data[0]))));
The Standard library contains some useful functions for working with uninitialized memory. The complete list can be found here (see the Uninitialized storage section).
Upvotes: 4