Consider a class with a member that can't be stored directly, e.g., because it does not have a default constructor, and the enclosing class's constructor doesn't have enough information to create it:
class Foo
{
public:
Foo(){} // Default ctor
private:
/* Won't build: no default ctor or way to call it's
non-default ctor at Foo's ctor. */
Bar m_bar;
};
Clearly, m_bar
needs to be stored differently, e.g., through a pointer. A std::unique_ptr
seems better, though, as it will destruct it automatically:
std::unique_ptr<Bar> m_bar;
It's also possible to use std::experimental::optional
, though:
std::experimenatl::optional<Bar> m_bar;
My questions are: 1. What are the tradeoffs? and 2. Does it make sense to build a class automating the choice between them?
Specifically, looking at the exception guarantees for the ctor of std::unique_ptr
and the exception guarantees for the ctor of std::experimental::optional
, it seems clear that the former must perform dynamic allocation and deallocation - runtime speed disadvantages, and the latter stores things in some (aligned) memory buffer - size disadvantages. Are these the only tradeoffs?
If these are indeed the tradeoffs, and given that both types share enough of their interface (ctor, operator*
), does it make sense to automate the choice between them with something like
template<typename T>
using indirect_raii = typename std::conditional<
// 20 - arbitrary constant
sizeof(std::experimental::optional<T>) >
20 + sizeof(std::exerimental::optional<T>)sizeof(std::unique_ptr<T>),
std::unique_ptr<T>,
std::experimental::optional<T>>::type;
(Note: there is a question discussing the tradeoffs between these two as return types, but the question and answers focus on what each conveys to the callers of the function, which is irrelevant for these private members.)
IMO there are other trade-offs at play here:
unique_ptr
is not copyable or copy-assignable, while optional
is.indirect_RAII
a class-type and conditionally add definitions to make it copyable by calling Bar
's copy ctor, even when unique_ptr
is selected. (Or conversely, disable copying when it's an optional.)optional
types can have a constexpr
constructor -- you can't really do the equivalent thing with a unique_ptr
at compile-time. Bar
can be incomplete at the time that unique_ptr<Bar>
is constructed. It cannot be incomplete at the time that optional<Bar>
is known. In your example I guess you assume that Bar
is complete since you take its size, but potentially you might want to implement a class using indirect_RAII
where this isn't the case.Bar
is large, you still may find that e.g. std::vector<Foo>
will perform better when optional
is selected than when unique_ptr
is. I would expect this to happen in cases where the vector
is populated once, and then iterated over many times. It may be that as a general rule of thumb, your size rule is good for common use in your program, but I guess for "common use" it doesn't really matter which one you pick. An alternative to using your indirect_RAII
type is, just pick one or the other in each case, and in places where you would have taken advantage of the "generic interface", pass the type as a template parameter when necessary. And in performance-critical areas, make the appropriate choice manually.