Reputation: 7068
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
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