I am reading "Effective Modern C++". In the item related to std::unique_ptr
it's stated that if the custom deleter is a stateless object, then no size fees occur, but if it's a function pointer or std::function
size fee occurs. Could you explain why?
Let's say that we have the following code:
auto deleter_ = [](int *p) { doSth(p); delete p; }; std::unique_ptr<int, decltype(deleter_)> up(new int, deleter_);
To my understanding, the unique_ptr
should have an object of type decltype(deleter_)
and assign deleter_
to that internal object. But obviously that's not what's happening. Could you explain the mechanism behind this using smallest possible code example?
std::unique_ptr::unique_ptrThe object is empty (owns nothing), with value-initialized stored pointer and stored deleter. construct from pointer (3) The object takes ownership of p, initializing its stored pointer to p and value-initializing its stored deleter.
This means that unique_ptr is exactly the same size as that pointer, either four bytes or eight bytes.
unique_ptr. An unique_ptr has exclusive ownership of the object it points to and will destroy the object when the pointer goes out of scope.
Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. A shared_ptr is a container for raw pointers.
A unique_ptr
must always store its deleter. Now, if the deleter is a class type with no state, then the unique_ptr
can make use of empty base optimization so that the deleter does not use any additional space.
How exactly this is done differs between implementations. For instance, both libc++ and MSVC store the managed pointer and the deleter in a compressed pair, which automatically gets you empty base optimization if one of the types involved is an empty class.
From the libc++ link above
template <class _Tp, class _Dp = default_delete<_Tp> > class _LIBCPP_TYPE_VIS_ONLY unique_ptr { public: typedef _Tp element_type; typedef _Dp deleter_type; typedef typename __pointer_type<_Tp, deleter_type>::type pointer; private: __compressed_pair<pointer, deleter_type> __ptr_;
libstdc++ stores the two in an std::tuple
and some Google searching suggests their tuple
implementation employs empty base optimization but I can't find any documentation stating so explicitly.
In any case, this example demonstrates that both libc++ and libstdc++ use EBO to reduce the size of a unique_ptr
with an empty deleter.
If the deleter is stateless there's no space required to store it. If the deleter is not stateless then the state needs to be stored in the unique_ptr
itself.std::function
and function pointers have information that is only available at runtime and so that must be stored in the object alongside the pointer the object itself. This in turn requires allocating (in the unique_ptr
itself) space to store that extra state.
Perhaps understanding the Empty Base Optimization will help you understand how this could be implemented in practice.
The std::is_empty
type trait is another possibility of how this could be implemented.
How exactly library writers implement this is obviously up to them and what the standard allows.
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