cppcoder
cppcoder

Reputation: 111

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?

Upvotes: 10

Views: 438

Answers (1)

Jeff Garrett
Jeff Garrett

Reputation: 7403

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.

Upvotes: 4

Related Questions