ciamej
ciamej

Reputation: 7068

Uninitialized memory wrapper

I'm looking for a class that is similar to std::optional, but without the internal flag which tells whether the container is empty or not. I want to be able to declare a variable of type T without invoking T' constructor, and later on move or emplace something into it on my discretion. Specifically I want to work with non-default-constructible T's.

It can be achieved easily with std::optional, but it comes with an overhead of the internal flag. I want this wrapper's size to be equal to sizeof(T).

I know such a class can be implemented using placement new (as are std::optional, std::variant etc). But it looks like a lot of work, and I'm wondering if something like that already exists...

Upvotes: 0

Views: 116

Answers (1)

user17732522
user17732522

Reputation: 76819

There is nothing for it in the standard library, but it is relatively straight-forward to write such an unsafe optional as a union class. It still requires that you implement the constructor and methods with a placement-new (or construct_at).

However, such a class can't follow the RAII principle properly, because the destructor cannot assume that the unsafe optional is non-empty, so that it can't destroy the contained object. Instead the user of the unsafe optional has to manually choose to destruct the contained object before the unsafe optional's lifetime ends or before a new object is emplaced into it.

It would be preferably to rewrite the user code so that it isn't necessary to construct the empty unsafe optional first. The user code must know whether it contains an object anyway for the reason above, so it should always be possible. (I don't know your concrete use case, so I can't give concrete advice.)


From your comment it seems like you are writing a container. A container can use the standard Allocator concept together with std::allocator_traits as all the standard library allocator-aware containers (e.g. std::vector, std::map, etc.) do:

Your class takes a template parameter called A, usually defaulted to std::allocator<T> (the default allocator using operator new/operator delete), then define

using Alloc = typename std::allocator_traits<A>::template rebind<T>;

and store an instance alloc of Alloc as the allocator (possibly passed through a constructor or default-constructed).

Then to obtain memory you do

T* storage = std::allocator_traits<Alloc>::allocate(alloc, n);

where n is the number of elements to allocate memory for, without constructing any object.

Then to construct the i's object you do

std::allocator_traits<Alloc>::construct(alloc, &storage[i], /*constructor args*/);

To destruct the object you do

std::allocator_traits<Alloc>::destroy(alloc, &storage[i]);

and to deallocate the memory you do

std::allocator_traits<Alloc>::deallocate(alloc, n);

where n must be the same as the allocation size.

That way your container will automatically support all classes as allocator that follow the standard's Allocator concept and no dangerous casts or anything like that is required.

Upvotes: 2

Related Questions