weak_ptr smart shared_ptr pointer example create c++ c++11 shared-ptr smart-pointers move-semantics

c++ - smart - ¿Por qué debería std:: mover un std:: shared_ptr?



unique pointer (5)

He estado buscando el código fuente de Clang y encontré este fragmento:

void CompilerInstance::setInvocation( std::shared_ptr<CompilerInvocation> Value) { Invocation = std::move(Value); }

¿Por qué querría std::move un std::shared_ptr ?

¿Hay algún punto para transferir la propiedad de un recurso compartido?

¿Por qué no haría esto en su lugar?

void CompilerInstance::setInvocation( std::shared_ptr<CompilerInvocation> Value) { Invocation = Value; }


Al usar move evitas aumentar y luego disminuir inmediatamente el número de acciones. Eso podría ahorrarle algunas operaciones atómicas costosas en el recuento de uso.


Copiar un shared_ptr implica copiar su puntero de objeto de estado interno y cambiar el recuento de referencia. Moverlo solo implica intercambiar punteros al contador de referencia interno y al objeto propio, por lo que es más rápido.


Creo que lo único que las otras respuestas no enfatizaron lo suficiente es el punto de la velocidad .

std::shared_ptr referencia std::shared_ptr es atómico . aumentar o disminuir el recuento de referencia requiere un incremento o decremento atómico . Esto es cien veces más lento que el incremento / decremento no atómico , sin mencionar que si incrementamos y disminuimos el mismo contador, terminamos con el número exacto, perdiendo una tonelada de tiempo y recursos en el proceso.

Al mover shared_ptr lugar de copiarlo, "robamos" el recuento de referencia atómica y shared_ptr el otro shared_ptr . "robar" el recuento de referencia no es atómico , y es cien veces más rápido que copiar shared_ptr (y causar un incremento o decremento de referencia atómica ).

Tenga en cuenta que esta técnica se utiliza exclusivamente para la optimización. copiarlo (como sugirió) es tan bueno en cuanto a funcionalidad.


Hay dos razones para usar std :: move en esta situación. La mayoría de las respuestas abordaron la cuestión de la velocidad, pero ignoraron la cuestión importante de mostrar la intención del código más claramente.

Para un std :: shared_ptr, std :: move denota inequívocamente una transferencia de propiedad del puntero, mientras que una operación de copia simple agrega un propietario adicional. Por supuesto, si el propietario original posteriormente renuncia a su propiedad (por ejemplo, al permitir que se destruya su std :: shared_ptr), se ha realizado una transferencia de propiedad.

Cuando transfiere la propiedad con std :: move, es obvio lo que está sucediendo. Si usa una copia normal, no es obvio que la operación prevista es una transferencia hasta que verifique que el propietario original renuncie inmediatamente a la propiedad. Como beneficio adicional, es posible una implementación más eficiente, ya que una transferencia atómica de propiedad puede evitar el estado temporal en el que el número de propietarios ha aumentado en uno (y los cambios correspondientes en los recuentos de referencia).


Las operaciones de movimiento (como move constructor) para std::shared_ptr son baratas , ya que básicamente son "robo de punteros" (de origen a destino; para ser más precisos, todo el bloque de control de estado es "robado" de origen a destino, incluido el información del recuento de referencia).

En cambio, las operaciones de copia en std::shared_ptr invocan el aumento del recuento de referencias atómicas (es decir, no solo ++RefCount en un miembro de datos entero RefCount , sino, por ejemplo, llamar a InterlockedIncrement en Windows), que es más costoso que simplemente robar punteros / estado.

Entonces, analizando la dinámica de recuento de ref de este caso en detalle:

// shared_ptr<CompilerInvocation> sp; compilerInstance.setInvocation(sp);

Si pasa sp por valor y luego toma una copia dentro del método CompilerInstance::setInvocation , tiene:

  1. Al ingresar al método, el parámetro shared_ptr se construye con copia: ref count atomic increment .
  2. Dentro del cuerpo del método, copie el parámetro shared_ptr en el miembro de datos: incremento atómico de recuento de referencia.
  3. Al salir del método, el parámetro shared_ptr se destruye: decremento atómico de recuento de ref.

Tiene dos incrementos atómicos y un decremento atómico, para un total de tres operaciones atómicas .

En cambio, si pasa el parámetro shared_ptr por valor y luego std::move dentro del método (como se hace correctamente en el código de Clang), tiene:

  1. Al ingresar al método, el parámetro shared_ptr se construye con copia: ref count atomic increment .
  2. Dentro del cuerpo del método, std::move el parámetro shared_ptr al miembro de datos: ¡el recuento de referencias no cambia! Solo está robando punteros / estado: no hay operaciones costosas de conteo de ref. Atómica.
  3. Al salir del método, el parámetro shared_ptr se destruye; pero como te shared_ptr en el paso 2, no hay nada que destruir, ya que el parámetro shared_ptr ya no apunta a nada. Nuevamente, no ocurre decremento atómico en este caso.

En pocas palabras: en este caso, obtienes solo un incremento atómico de recuento de referencia, es decir, solo una operación atómica .
Como puede ver, esto es mucho mejor que dos incrementos atómicos más un decremento atómico (para un total de tres operaciones atómicas) para el caso de copia.