Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Effectiveness of unique_ptr

I liked std::unique_ptr the moment I saw it back in c++11, but I questioned it's effectiveness for quite a while. (see link below for link to live code) :

#include <memory>

std::unique_ptr<int> get();
extern std::unique_ptr<int> val;

void foo()
{
    val = get();
}

This gave me 16 instructions on last clang with -O3. But what is more interesting is that it generated two calls to delete, even though the second one will never be called.

Than I tried to do it like this:

void foo()
{
    auto ptr = get().release();
    val.reset(ptr);
}

And suddenly it's just 11 instructions. Then I went deeper and hacked unique_ptr move ctor. Originally it is implemented as reset(__u.release());. I basically just reordered it as follows:

auto& ptr =  _M_ptr();
if (ptr)
    _M_deleter()(ptr);
ptr = __u.release();

Aaand.... 11 instructions as in hand-managed version. It is slightly different but seems ok.

I saved my experiments here.

Can someone point out is that it's I'm missing something or it's actually somehow intended?

like image 802
cppcoder Avatar asked Apr 29 '21 19:04

cppcoder


People also ask

Why is unique_ptr useful?

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 Reset do?

std::unique_ptr::reset Destroys the object currently managed by the unique_ptr (if any) and takes ownership of p. If p is a null pointer (such as a default-initialized pointer), the unique_ptr becomes empty, managing no object after the call.

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.

Can you pass unique_ptr by value?

Because the unique pointer does not have a copy constructor. Hence you cannot pass it by value, because passing by value requires making a copy. Actually that is nearly the sole and whole point of a unique_ptr .


1 Answers

The order of operations for move-assignment must be:

  1. Make a copy of the managed source pointer, and null it in the source object.
  2. If the destination managed pointer is not null, delete it.
  3. Set the destination managed pointer in the destination object.

Note that this logic happily results in a no-op for self-move-assignment.

The reason the logic must be this way is the source may be owned by the destination.

Imagine:

struct list { std::unique_ptr<list> next; };
std::unique_ptr<list> head;
// ...
if (head) head = std::move(head->next);

This will not behave correctly if head->next's managed pointer isn't nulled before deleting the old object managed by head.

In code, move assignment therefore is just:

reset(source.release())

Your last snippet obviously fails to null the source before deletion, and is thus not a viable implementation for move assignment:

auto& ptr =  _M_ptr();
if (ptr)
    _M_deleter()(ptr);
ptr = __u.release();

That leaves your question about why

val = get();

is different than

auto ptr = get().release();
val.reset(ptr);

The difference is in the implicit unique_ptr<int> returned from get(). In the first version, it is destroyed after both the release and reset. In the second version it is destroyed after release but before reset. In both cases, it is nulled by the time it is destroyed, and there need not be a delete. But the compiler must be unable to propagate the knowledge that this pointer was nulled across the reset.

like image 188
Jeff Garrett Avatar answered Oct 21 '22 11:10

Jeff Garrett