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;
}
fuente
Al usarlo
move
, evita aumentar y luego disminuir inmediatamente el número de acciones. Eso podría ahorrarle algunas operaciones atómicas costosas en el recuento de uso.fuente
Las operaciones de movimiento (como el constructor de movimientos)
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, incluida la información de recuento de referencia) .En cambio, las operaciones de copia en
std::shared_ptr
invocación aumentan el recuento de referencia atómica (es decir, no solo++RefCount
en unRefCount
miembro de datos enteros , sino, por ejemplo,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:
Si pasa
sp
por valor y luego toma una copia dentro delCompilerInstance::setInvocation
método, tiene:shared_ptr
es un parámetro copia construidos: ref contar atómica de la subasta .shared_ptr
parámetro en el miembro de datos: incremento atómico de recuento de referencia .shared_ptr
parámetro 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
shared_ptr
parámetro por valor y luegostd::move
dentro del método (como se hace correctamente en el código de Clang), tiene:shared_ptr
es un parámetro copia construidos: ref contar atómica de la subasta .std::move
elshared_ptr
parámetro en el miembro de datos: ¡el recuento de referencias no cambia! Solo está robando punteros / estado: no hay operaciones costosas de conteo de ref.shared_ptr
parámetro se destruye; pero como te moviste en el paso 2, no hay nada que destruir, ya que elshared_ptr
parámetro ya no apunta a nada. Una vez más, no se produce una disminución atómica 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.
fuente
compilerInstance.setInvocation(std::move(sp));
no habrá incremento . Puede obtener el mismo comportamiento agregando una sobrecarga que requiere unshared_ptr<>&&
pero por qué duplicar cuando no es necesario.setInvocation(new CompilerInvocation)
, o como se mencionó trinquete,setInvocation(std::move(sp))
. Lo siento si mi primer comentario no fue claro, en realidad lo publiqué por accidente, antes de terminar de escribir, y decidí dejarloCopiar 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.fuente
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 simple operación de copia 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).
fuente
Al menos con libstdc ++ debería obtener el mismo rendimiento con mover y asignar porque
operator=
llamastd::move
al puntero entrante. Ver: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/shared_ptr.h#L384fuente