Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Atomic operations on `unique_ptr`

std::shared_ptr has specializations for atomic operations like atomic_compare_exchange_weak and family, but I cannot find documentation on equivalent specializations for std::unique_ptr. Are there any? If not, why not?

like image 803
J. Doe Avatar asked Jan 28 '16 17:01

J. Doe


People also ask

Is unique_ptr Atomic?

No there no standard atomic functions for std::unique_ptr .

What happens when unique_ptr goes out of scope?

An​ unique_ptr has exclusive ownership of the object it points to and ​will destroy the object when the pointer goes out of scope. A unique_ptr explicitly prevents copying of its contained pointer. Instead, the std::move function has to be used to transfer ownership of the contained pointer to another unique_ptr .

Should I use unique_ptr or shared_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.

What does unique_ptr get do?

unique_ptr::getReturns a pointer to the managed object or nullptr if no object is owned.


1 Answers

The reason that it is possible to provide an atomic instance of std::shared_ptr and it is not possible to do so for std::unique_ptr is hinted at in their signature. Compare:

  • std::shared_ptr<T> vs
  • std::unique_ptr<T, D> where D is the type of the Deleter.

std::shared_ptr needs to allocate a control-block where the strong and weak count are kept, so type-erasure of the deleter came at a trivial cost (a simply slightly larger control-block).

As a result, the layout of std::shared_ptr<T> is generally similar to:

template <typename T>
struct shared_ptr {
    T* _M_ptr;
    SomeCounterClass<T>* _M_counters;
};

And it is possible to atomically perform the exchange of those two pointers.


std::unique_ptr has a zero-overhead policy; using a std::unique_ptr should not incur any overhead compared to using a raw pointer.

As a result, the layout of std::unique_ptr<T, D> is generally similar to:

template <typename T, typename D = default_delete<T>>
struct unique_ptr {
    tuple<T*, D> _M_t;
};

Where the tuple uses EBO (Empty Base Optimization) so that whenever D is zero-sized then sizeof(unique_ptr<T>) == sizeof(T*).

However, in the cases where D is NOT zero-sized, the implementation boils down to:

template <typename T, typename D = default_delete<T>>
struct unique_ptr {
    T* _M_ptr;
    D _M_del;
};

This D is the kicker here; it is not possible, in general, to guarantee that D can be exchange in an atomic fashion without relying on mutexes.

Therefore, it is not possible to provide an std::atomic_compare_exchange* suite of specialized routine for the generic std::unique_ptr<T, D>.

Note that the standard does not even guarantee that sizeof(unique_ptr<T>) == sizeof(T*) AFAIK, though it's a common optimization.

like image 125
Matthieu M. Avatar answered Sep 22 '22 16:09

Matthieu M.