Reputation: 111
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
Reputation: 7403
The order of operations for move-assignment must be:
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