I admit it: I'm in love with the concept of optional. The quality of my code has improved so much ever since I discovered it. Making it explicit whether a variable may or may not be valid is so much better than plain error codes and in-band signaling. It also allows me to not worry about having to read the contract in the documentation, or worrying about whether it's up-to-date: the code itself is the contract.
That said, sometimes I need to deal with std::unique_ptr
. Objects of this type might be null or not; at a given point in the code is impossible to know whether the std::unique_ptr
is supposed to have a value or not; it's impossible to know the contract from the code.
I would like to somehow mix optional
(maybe withboost::optional
) and std::unique_ptr
, so that I have a dynamically allocated object with scope-destruction and proper copy/move behaviour that explicitly states that it may not have a value. That way, I can use this new type to make it explicit that a check for value is necessary and avoid unnecessary checks for plain std::unique_ptr
.
Is there a tool for this inside the C++11 standard, boost
or a popular enough library? I could accept defining my own class for this, but that would be the least preferred method (due to lack of thorough testing).
So to recap your question, you want:
boost::optional
for this (or you can use std::optional
from C++17).You are unhappy that you can express the difference between 1 and 2, but both 3 and 4 usually use the same type (std::unique_ptr
). You suggest using std::unique_ptr
for 3, never allowing nullptr
, and some other thing for 4, but want to know what you can use. (In the comments you also accept the possibility of using std::unique_ptr
with nullptr
for 4 if something else can be found for 3.)
Literal answer to your question: you can simply use boost::optional<std::unique_ptr<T>>
for 4 (while using a bare unique_ptr
for 3 as you suggested).
Alternative literal answer to your question: As @StoryTeller said, you could define your own smart pointer type that is like unique_ptr
but disallows nullptr
, and use that for 3. A quicker (but very dirty) alternative is to force functions to return a pair
of both a unique_ptr
and a reference to that same object. Then only access the result through the reference, but only do so while the unique_ptr
still exists:
template<class T>
using RefAndPtr = std::pair<T&, std::unique_ptr<T>>;
RefAndPtr<Foo> getFoo()
{
std::unique_ptr<Foo> result = std::make_unique<Foo>();
return RefAndPtr<Foo>(*result, std::move(result));
}
My actual suggestion: Just suck it up and use std::unique_ptr
for both 3 and 4. Clarifying your intentions in the type system is a good thing, but too much of a good thing can be bad. Using either of the above options is just going to confuse the hell out of anyone that reads your code. And even if you stop people from incorrectly passing around nullptr
, what's to stop them passing a pointer around to the wrong object, or already-freed memory, etc.? At some point you have to specify things outside of the type system.
std::unique_ptr
is nullable. It becomes null whenever moved-from, or when default constructed.
std::unique_ptr
is your nullable heap allocated object.
A value_ptr
can be written that is not nullable. Note that there are extra costs at move:
template<class T>
class value_ptr {
struct ctor_key_token{ explicit ctor_key_token(int){} };
public:
template<class A0, class...Args, class dA0 = std::decay_t<A0>,
std::enable_if_t<!std::is_same<dA0, ctor_key_token>{} && !std::is_same<dA0, value_ptr>{}, int> = 0
>
value_ptr( A0&& a0, Args&&... args):
value_ptr( ctor_key_token(0), std::forward<A0>(a0), std::forward<Args>(args)... )
{}
value_ptr(): value_ptr( ctor_key_token(0) ) {}
template<class X, class...Args>
value_ptr( std::initializer_list<X> il, Args&&... args ):
value_ptr( ctor_key_token(0), il, std::forward<Args>(args)... )
{}
value_ptr( value_ptr const& o ):
value_ptr( ctor_key_token(0), *o.state )
{}
value_ptr( value_ptr&& o ):
value_ptr( ctor_key_token(0), std::move(*o.state) )
{}
value_ptr& operator=(value_ptr const& o) {
*state = *o.state;
return *this;
}
value_ptr& operator=(value_ptr && o) {
*state = std::move(*o.state);
return *this;
}
T* get() const { return state.get(); }
T* operator->() const { return get(); }
T& operator*() const { return *state; }
template<class U,
std::enable_if_t<std::is_convertible<T const&, U>{}, int> =0
>
operator value_ptr<U>() const& {
return {*state};
}
template<class U,
std::enable_if_t<std::is_convertible<T&&, U>{}, int> =0
>
operator value_ptr<U>() && {
return {std::move(*state)};
}
private:
template<class...Args>
value_ptr( ctor_key_token, Args&&... args):
state( std::make_unique<T>(std::forward<Args>(args)...) )
{}
std::unique_ptr<T> state;
};
that is a rough sketch of a non-nullable heap-allocated value semantics object.
Note that when you move-from it, it doesn't free the old memory. The only time it doesn't own a T
on the heap is during construction (which can only abort via a throw) and during destruction (as state
is destroyed).
Fancier versions can have custrom destroyers, cloners and movers, permitting polymorphic value semantic types or non-copyable types to be stored.
Using types that are almost-never-null or rarely-null as never-null leads to bugs. So don't do it.
Live example.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With