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?
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.
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.
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.
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 .
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With