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));
}
};
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).
Related
I have a C++ Object class like this:
class Component {};
template <typename T>
concept component = std::is_base_of_v<Component, T>;
class Object
{
std::map<std::type_index, Component*> components;
public:
template<component T>
T* add()
{
if(components.find(typeid(T)) == components.cend())
{
T* value{new T{}};
components[typeid(T)] = static_cast<Component*>(value);
}
}
template<component T, typename... Args>
T* add(Args &&... args)
{
if(components.find(typeid(T)) == components.cend())
{
T* value{new T{std::forward<Args>(args)...}};
components[typeid(T)] = static_cast<Component*>(value);
}
}
};
Components that are added to class Object are deleted on another function that is not related to my question. AFAIK doing a lot of new/delete calls (heap allocations) hurt performance and supposedly there should be like 20/30 (or even more) Objectss with 3-10 Object::add on each one. I thought that I could just call T-s constructor without new, then to static_cast<Component*>(&value), but the Component added on the map is "invalid", meaning all T's members (ex. on a class with some int members, they are all equal to 0 instead of some custom value passed on its constructor). I am aware that value goes out of scope and the pointer on the map becomes a dangling one, but I can't find a way to instantiate T objects without calling new or without declaring them as static. Is there any way to do this?
EDIT: If I declare value as static, everything works as expected, so I guess its a lifetime issue related to value.
I suppose, you think of this as the alternative way of creating your objects
T value{std::forward<Args>(args)...};
components[typeid(T)] = static_cast<Component*>(&value);
This creates a local variable on the stack. Doing the assignment then, stores a pointer to a local variable in the map.
When you leave method add(), the local object will be destroyed, and you have a dangling pointer in the map. This, in turn, will bite you eventually.
As long as you want to store pointers, there's no way around new and delete. You can mitigate this a bit with some sort of memory pool.
If you may also store objects instead of pointers in the map, you could create the components in place with std::map::emplace. When you do this, you must also remove the call to delete and clean up the objects some other way.
Trying to avoid heap allocations before you've proven that they indeed hurt your programs' performance is not a good approach in my opinion. If that was the case, you should probably get rid of std::map in your code as well. That being said, if you really want to have no new/delete calls there, it can be done, but requires explicit enumeration of the Component types. Something like this could be what you are looking for:
#include <array>
#include <variant>
// Note that components no longer have to implement any specific interface, which might actually be useful.
struct Component1 {};
struct Component2 {};
// Component now is a variant enumerating all known component types.
using Component = std::variant<std::monostate, Component1, Component2>;
struct Object {
// Now there is no need for std::map, as we can use variant size
// and indexes to create and access a std::array, which avoids more
// dynamic allocations.
std::array<Component, std::variant_size_v<Component> - 1> components;
bool add (Component component) {
// components elements hold std::monostate by default, and holding std::monostate
// is indicated by returning index() == 0.
if (component.index() > 0 && components[component.index() - 1].index() == 0) {
components[component.index() - 1] = std::move(component);
return true;
}
return false;
}
};
Component enumerates all known component types, this allows to avoid dynamic allocation in Object, but can increase memory usage, as the memory used for single Object is roughly number_of_component_types * size_of_largest_component.
While the other answers made clear what the problem is I want to make a proposition how you could get around this in its entirety.
You know at compile time what possible types will be in the map at mosz, since you know which instantation of the add template where used. Hence you can get rid of the map and do all in a compile time.
template<component... Comps>
struct object{
std::tuple<std::optional<Comps>...> components;
template<component comp, class ... args>
void add(Args... &&args) {
std::get<std::optional<comp>>(components).emplace(std::forward<Args>(args)...);
}
}
Of course this forces you to collect all the possible objects when you create the object, but this not more info you have to have just more impractical.
You could add the following overload for add to make the errors easier to read
template<component T>
void add(...) {
static_assert(false, "Please add T to the componentlist of this object");
}
I came across a thread-safe stack implementation of an interface method for stack<>::pop():
void pop(T& value)
{
std::lock_guard<std::mutex> lock(m);
if(data.empty()) throw empty_stack();
value=std::move(data.top()); <----- why not just value = data.top()?
data.pop();
}
Granted, my question has nothing to do with concurrency, but why MOVE the value at the top of the stack into variable value? My understanding is once it is moved, you can't pop it as it is no longer there.
Either it is a mistake in the source where I found it, or if someone can explain it to me I would be thankful.
Thanks,
Amine
value=std::move(data.top()); <----- why not just value = data.top()?
This largely depends on what T is, basically it is going to try and use the move constructor, T(T &&mv) instead of the copy constructor T(const T &cp) if the move constructor exists.
In both cases ~T() will be called for the original object on the data.pop(); line.
Firstly, using the move constructor might be required. Some objects are moveable, but not copyable, e.g. a unique_ptr.
Secondly, where move is provided, it is often more efficient. e.g. say T is a std::vector, the copy constructor will allocate another array and then copy over each element (which might also be expensive), and then it deletes the original array anyway. Which is pretty wasteful.
A move constructor just keeps the original elements by moving the internal data array from one vector to a new one, and leaves the original (which is about to be deleted anyway in this case) in an unspecified but valid state (probably "empty").
It might look something like:
template<typename T> class vector
{
public:
vector<T>(vector<T> &&mv)
: arr(mv.arr) , arr_len(mv.arr_len), arr_capacity(mv.arr_capacity)
{
mv.arr = nullptr;
mv.arr_len = 0;
mv.arr_capacity = 0;
}
...
private:
T *arr;
size_t arr_len;
size_t arr_capacity;
};
Since the original object state is "unspecified" in the general case if you want to keep using the original object, you have to be careful. Destroying it as in the pop case is OK, as is assignment.
T tmp = std::move(some_value);
some_value.foo(); // In general, what state some_value is in is unknown, this might vary even from compiler to compiler
some_value = some_other_value; // But assignment should work
some_value.foo(); // So it is now in a known state
Which can implement for example a "swap" without copying any "contents" where possible.
template<typename T> void swap(T &a, T &b)
{
T tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
}
Many types will be more specified, for example for std::vector, it promises to be empty().
std::vector<T> tmp = std::move(some_array);
assert(some_array.empty()); // guaranteed
some_array.push_back(x); // guaranteed to have one element
you can't pop it as it is no longer there.
So the important bit here is that data.top() does not remove the element, so it is still there. And the move doesn't actually delete the thing, just leaves it in some unspecified state.
Concurrency-safe stack interface method
On the separate subject of concurrency the thing here is both the top to access the value, and the pop to remove it are under the same lock. To be safe all accesses to this instance stack must use that same lock instance, so make sure any data.push also holds the lock.
Given the following types
// interface and implementation used in one part of the codebase
struct Image
{
virtual std::vector<uint8_t>& GetData () = 0;
};
struct VecImage : public Image
{
std::vector<uint8_t> mData;
std::vector<uint8_t>& GetData () { return mData; }
};
// used in another part of the codebase
struct PtrImage
{
std::shared_ptr<uint8_t> mData;
PtrImage (std::shared_ptr<Image> pIm);
};
is the following constructor a sane and correct way to convert an Image to a PtrImage?
PtrImage::PtrImage (std::shared_ptr<Image> pIm)
{
struct im_deleter
{
std::shared_ptr<Image> keepAlive;
void operator () (uint8_t* ptr)
{
keepAlive.reset ();
}
};
mData = { &pIm->GetData()[0], im_deleter { pIm } };
}
PtrImage is used as a "value type", it is being passed around by value, while Image is passed around in shared_ptrs only.
is the following constructor a sane..
You extend lifetime of Image thanks to destructor, so data is still valid.
So you are correct on that point...
But, vector may reallocate, invalidating the buffer.
So resulting code is unsafe.
You could store std::shared_ptr<std::vector<uint8_t>> mData; to be safe.
.. and correct way
We have better/simpler with aliasing constructor of std::shared_ptr:
struct PtrImage
{
std::shared_ptr<std::vector<uint8_t>> mData;
PtrImage (std::shared_ptr<Image> pIm) : mData(pIm, &pIm->GetData()) {}
};
So ownership information PtrImage::mData is shared with pIm.
Note: I assumes that vector returned by GetData() has same (or longer) lifetime that Image (as for VecImage). if it is an unrelated vector (from other object), then you won't have solution.
As noted in comment, vector should not reallocate neither
Looks pretty dangerous to me:
std::shared_ptr<Image> i = std::make_shared<VecImage>(/* some data */);
PtrImage p(i); // has now stored a pointer to the vector's data
i->getData()->push_back(0); // repeat until re-allocation occurs!
What would p now hold? The shared pointer holds a pointer to the data that resided in the vector before re-allocation; but this data was replaced and got deleted. So you now have a dangling pointer stored in p (in the uint8_t pointer), using it (which will happen at latest when your smart pointer tries to delete its data) will result in undefined behaviour.
You should not even try to have a shared pointer guess whether it should delete its object or not. It you proceed that way, you will be caught at a time by a corner case or even if you managed to find all, by a new one created by an apparently unrelated change.
If you need to convert an Image to a PtrImage just say that you want to build a shared_ptr<T> from a T. There are 2 standard ways: a copy or a move, and in either the shared_ptr has ownership of its object.
In your example, as Image only returns a lvalue reference, you can only use a copy. You could have a move from a VecImage by taking ownership of its data member.
My teammates are writing a fixed-size implementation of std::vector for a safety-critical application. We're not allowed to use heap allocation, so they created a simple array wrapper like this:
template <typename T, size_t NUM_ITEMS>
class Vector
{
public:
void push_back(const T& val);
...more vector methods
private:
// Internal storage
T storage_[NUM_ITEMS];
...implementation
};
A problem we encountered with this implementation is that it requires elements present default constructors (which is not a requirement of std::vector and created porting difficulties). I decided to hack on their implementation to make it behave more like std::vector and came up with this:
template <typename T, size_t NUM_ITEMS>
class Vector
{
public:
void push_back(const T& val);
...more vector methods
private:
// Internal storage
typedef T StorageType[NUM_ITEMS];
alignas(T) char storage_[NUM_ITEMS * sizeof(T)];
// Get correctly typed array reference
StorageType& get_storage() { return reinterpret_cast<T(&)[NUM_ITEMS]>(storage_); }
const StorageType& get_storage() const { return reinterpret_cast<const T(&)[NUM_ITEMS]>(storage_); }
};
I was then able to just search and replace storage_ with get_storage() and everything worked. An example implementation of push_back might then look like:
template <typename T, size_t NUM_ITEMS>
void Vector<T, NUM_ITEMS>::push_back(const T& val)
{
get_storage()[size_++] = val;
}
In fact, it worked so easily that it got me thinking.. Is this a good/safe use of reinterpret_cast? Is the code directly above a suitable alternative to placement new, or are there risks associated with copy/move assignment to an uninitialized object?
EDIT: In response to a comment by NathanOliver, I should add that we cannot use the STL, because we cannot compile it for our target environment, nor can we certify it.
The code you've shown is only safe for POD types (Plain Old Data), where the object's representation is trivial and thus assignment to an unconstructed object is ok.
If you want this to work in all generality (which i assume you do due to using a template), then for a type T it is undefined behavior to use the object prior to construction it. That is, you must construct the object before your e.g. assignment to that location. That means you need to call the constructor explicitly on demand. The following code block demonstrates an example of this:
template <typename T, size_t NUM_ITEMS>
void Vector<T, NUM_ITEMS>::push_back(const T& val)
{
// potentially an overflow test here
// explicitly call copy constructor to create the new object in the buffer
new (reinterpret_cast<T*>(storage_) + size_) T(val);
// in case that throws, only inc the size after that succeeds
++size_;
}
The above example demonstrates placement new, which takes the form new (void*) T(args...). It calls the constructor but does not actually perform an allocation. The visual difference is the inclusion of the void* argument to operator new itself, which is the address of the object to act on and call the constructor for.
And of course when you remove an element you'll need to destroy that explicitly as well. To do this for a type T, simply call the pseudo-method ~T() on the object. Under templated context the compiler will work out what this means, either an actual destructor call, or no-op for e.g. int or double. This is demonstrated below:
template<typename T, size_t NUM_ITEMS>
void Vector<T, NUM_ITEMS>::pop_back()
{
if (size_ > 0) // safety test, you might rather this throw, idk
{
// explicitly destroy the last item and dec count
// canonically, destructors should never throw (very bad)
reinterpret_cast<T*>(storage_)[--size_].~T();
}
}
Also, I would avoid returning a refernce to an array in your get_storage() method, as it has length information and would seem to imply that all elements are valid (constructed) objects, which of course they're not. I suggest you provide methods for getting a pointer to the start of the contiguous array of constructed objects, and another method for getting the number of constructed objects. These are the .data() and .size() methods of e.g. std::vector<T>, which would make use of your class less jarring to seasoned C++ users.
Is this a good/safe use of reinterpret_cast?
Is the code directly above a suitable alternative to placement new
No. No.
or are there risks associated with copy/move assignment to an uninitialized object?
Yes. The behaviour is undefined.
Assuming memory is uninitialised, copying the vector has undefined behaviour.
No object of type T has started its lifetime at the memory location. This is super bad when T is not trivial.
The reinterpretation violates the strict aliasing rules.
First is fixed by value-initialising the storage. Or by making the vector non-copyable and non-movable.
Second is fixed by using placement new.
Third is technically fixed by using using the pointer returned by placement new, but you can avoid storing that pointer by std::laundering after reinterpreting the storage.
Consider, that I have initialised the unique_ptr like this:
unique_ptr<uint_8[]> pixels{nullptr};
After that, I have decided to assign a new array:
pixels = new uint_8[10];
Unfortunately, it does not allow to assign a new array of size 10*8.
I do know, that I can simply assign std::make_unique<uint_8[]>(10) but I just want to understand the smart pointers.
So basically, the questions are:
Why it does not allow the cast from nullptr to a new array?
Is nullptr some specific type in C++11?
pixels = new uint_8[10];
This implies that operator= of std::unique_ptr will be used. However, if you take a look at all operator= overloads you will notice that there is no overload taking T* for std::unique_ptr<T[]>.
The method you need is called reset and it resets the old value stored in std::unique_ptr with the newer one:
pixels.reset(new uint_8[10]);
Ehm, correct me if I am wrong, but the OP asked how to "assign". It is a very specific question, with a very specific title too. The method the OP needs is perhaps not called "reset".
// replaces the current payload with the new empty one
pixels.reset(new uint_8[10]);
It seems the above does not "assign" anything to pixels if I am not confused with the meaning of the term "to assign".
I boldly assume the following might be to inquirer's liking:
// array of uint8_t elements
using u8arr = std::uint8_t[];
// instance of the said array
// that contains 3 uint8 values
u8arr uar{0,1,2};
// declare unique_ptr to the same array type
std::unique_ptr<u8arr> smart_arr_;
// at last ! we assign to the "smart array"
assign(smart_arr_, uar);
// the proof of the pudding
::wprintf(L"\n\n%d\t%d\t%d", smart_arr_[0],smart_arr_[1],smart_arr_[2]);
And as if by magic, I have one implementation of "assign" ready (I have prepared earlier):
template<typename C, size_t N>
inline auto
assign
(
std::unique_ptr<C[]> & sp_,
const C(& arr)[N]
) noexcept
-> std::unique_ptr<C[]> &
{
static_assert(std::is_trivially_copyable_v<C>);
sp_.release();
sp_ = std::make_unique<C[]>(N + 1);
void * rez_ = ::memcpy(sp_.get(), arr, N);
assert(rez_);
return sp_;
}
Just to make sure the good old memcpy will not object, I have used that static_assert to check on the type to be copied.