Hex Crown
Hex Crown

Reputation: 763

C++ proper way to move element of aligned_storage array

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

Answers (1)

Evg
Evg

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 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?

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

Related Questions