Search code examples
c++smart-pointersunique-ptr

Pop method of linked list using unique_ptr


I'm looking at the implementation of a singly linked list using unique_ptr on https://solarianprogrammer.com/2019/02/22/cpp-17-implementing-singly-linked-list-smart-pointers/. My question pertains to the following method:

 3 struct List {
 4     List() : head{nullptr} {};
 5 
 6     // ...
 7 
 8     void pop() {
 9         if(head == nullptr) {
10             return;
11         }
12 
13         std::unique_ptr<Node> temp = std::move(head);
14         head = std::move(temp->next);
15     }
16 
17     // ...
18 };

I am wondering why the temporary is needed here? Why couldn't you simply do head = std::move(head->next)? Is this because it will result in a memory leak? When head is reassigned, does unique_ptr automatically free the current memory it's pointing to?

I was under the impression that smart pointers are fool proof from memory leaks. It seems in this case there might be a memory leak for the original head because there would no longer be a smart pointer pointing to it?


Solution

  • I am wondering why the temporary is needed here?

    It is not really needed, but it is not bad to use, either.

    Why couldn't you simply do head = std::move(head->next)? Is this because it will result in a memory leak?

    You can. There will be no leak in this example.

    When head is reassigned, does unique_ptr automatically free the current memory it's pointing to?

    Yes. However, the old pointer will not be delete'd until after ownership of the new pointer is transferred first. Per cppreference:

    https://en.cppreference.com/w/cpp/memory/unique_ptr/operator%3D

    Transfers ownership from r to *this as if by calling reset(r.release()) followed by an assignment of get_deleter() from std::forward<E>(r.get_deleter()).

    https://en.cppreference.com/w/cpp/memory/unique_ptr/reset

    Replaces the managed object.

    1. Given current_ptr, the pointer that was managed by *this, performs the following actions, in this order:

      1. Saves a copy of the current pointer old_ptr = current_ptr
      2. Overwrites the current pointer with the argument current_ptr = ptr
      3. If the old pointer was non-empty, deletes the previously managed object
        if(old_ptr) get_deleter()(old_ptr).

    So:

    In the case where temp is used, the pointer to the old node in head will first be moved to temp via its move constructor, resetting head to hold a nullptr. Then head.operator= will call next.release() and acquire that pointer. And then temp will go out of scope, delete'ing the old node.

    In the case where temp is not used, head.operator= will call next.release(), save its old pointer and replace it with the released pointer, and then delete the saved pointer.

    No leak either way.

    I was under the impression that smart pointers are fool proof from memory leaks.

    If used properly, yes.

    It seems in this case there might be a memory leak for the original head because there would no longer be a smart pointer pointing to it?

    There is no leak, as there is always a unique_ptr referring to the old node, until pop() exits and temp is destroyed, delete'ing the old node with it. Even if temp is omitted, the old node is still destroyed properly after ownership of its next pointer is transferred.