Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ std::unique_ptr : Why isn't there any size fees with lambdas?

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?

like image 273
jnbrq -Canberk Sönmez Avatar asked Oct 22 '15 20:10

jnbrq -Canberk Sönmez


People also ask

Is unique_ptr empty?

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.

How big is a unique_ptr?

This means that unique_ptr is exactly the same size as that pointer, either four bytes or eight bytes.

What happens when unique_ptr goes out of scope?

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.

When should we use unique_ptr?

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.


2 Answers

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.

like image 148
Praetorian Avatar answered Oct 07 '22 15:10

Praetorian


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.

like image 39
SirGuy Avatar answered Oct 07 '22 13:10

SirGuy