Pop método de lista enlazada usando unique_ptr

Aug 20 2020

Estoy viendo la implementación de una lista enlazada individualmente usando unique_ptronhttps://solarianprogrammer.com/2019/02/22/cpp-17-implementing-singly-linked-list-smart-pointers/. Mi pregunta se refiere al siguiente método:

 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 };

Me pregunto por qué se necesita el temporal aquí. ¿Por qué no pudiste simplemente hacer head = std::move(head->next)? ¿Es esto porque resultará en una pérdida de memoria? Cuando headse reasigna, ¿ unique_ptrlibera automáticamente la memoria actual a la que apunta?

Tenía la impresión de que los punteros inteligentes son infalibles contra las fugas de memoria. En este caso, parece que podría haber una pérdida de memoria para el original headporque ya no habría un puntero inteligente que lo señale.

Respuestas

4 RemyLebeau Aug 20 2020 at 07:36

Me pregunto por qué se necesita el temporal aquí.

Realmente no es necesario , pero tampoco está mal de usar.

¿Por qué no pudiste simplemente hacer head = std::move(head->next)? ¿Es esto porque resultará en una pérdida de memoria?

Puedes. No habrá fugas en este ejemplo.

Cuando headse reasigna, ¿ unique_ptrlibera automáticamente la memoria actual a la que apunta?

Sí. Sin embargo, el puntero anterior no será delete'd hasta que se transfiera primero la propiedad del puntero nuevo. Por cppreference:

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

Transfiere la propiedad de ra *thiscomo si se llamara reset(r.release())seguido de una asignación de get_deleter()de std::forward<E>(r.get_deleter()).

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

Reemplaza el objeto administrado.

  1. Dado current_ptr, el puntero que fue manejado por *this, realiza las siguientes acciones, en este orden:

    1. Guarda una copia del puntero actualold_ptr = current_ptr
    2. Sobrescribe el puntero actual con el argumentocurrent_ptr = ptr
    3. Si el puntero anterior no estaba vacío, elimina el objeto administrado anteriormente
      if(old_ptr) get_deleter()(old_ptr).

Asi que:

En el caso de que tempse use, el puntero al nodo anterior headse moverá primero a temptravés de su constructor de movimiento, restableciéndose headpara contener un nullptr. Luego head.operator=llamará next.release()y adquirirá ese puntero. Y luego tempsaldrá del alcance, delete'ing el nodo anterior.

En caso de que tempno se use, head.operator=llamará a next.release(), guardará su puntero anterior y lo reemplazará con el puntero liberado y luego deleteel puntero guardado.

No hay fugas de ninguna manera.

Tenía la impresión de que los punteros inteligentes son infalibles contra las fugas de memoria.

Si se usa correctamente , sí.

En este caso, parece que podría haber una pérdida de memoria para el original headporque ya no habría un puntero inteligente que lo señale.

No hay fuga, ya que siempre hay una unique_ptrreferencia al nodo anterior, hasta que pop()sale y tempse destruye, deletellevándose consigo el nodo anterior. Incluso si tempse omite, el nodo antiguo aún se destruye correctamente después de nexttransferir la propiedad de su puntero.