weak_ptr unique_ptr smart shared_ptr pointer example create c++ shared-ptr

smart - unique_ptr c++



Pasando const shared_ptr<T> y contra solo shared_ptr<T> como parĂ¡metro (6)

He estado leyendo muchas discusiones sobre problemas de rendimiento cuando los punteros inteligentes están involucrados en una aplicación. Una de las recomendaciones frecuentes es pasar un puntero inteligente como const y en lugar de una copia, como esto:

void doSomething(std::shared_ptr<T> o) {}

versus

void doSomething(const std::shared_ptr<T> &o) {}

Sin embargo, ¿la segunda variante no derrota realmente el propósito de un puntero compartido? En realidad, estamos compartiendo el puntero compartido aquí, por lo que si por alguna razón el puntero se libera en el código de llamada (piense en la reentrada o en los efectos secundarios), el puntero constante se vuelve inválido. Una situación que el puntero compartido realmente debería evitar. Entiendo que const y ahorra tiempo ya que no hay copia involucrada y no hay bloqueo para administrar el recuento de ref. Pero el precio hace que el código sea menos seguro, ¿verdad?


Como se señaló en C ++ - shared_ptr: velocidad horrible , copiar tiempo en shared_ptr lleva tiempo. La construcción implica un incremento atómico y la destrucción un decremento atómico, una actualización atómica (ya sea incremento o decremento) puede evitar una serie de optimizaciones del compilador (las cargas / tiendas de memoria no pueden migrar a través de la operación) y en el nivel de hardware involucra el protocolo de coherencia de caché de la CPU para garantizar que toda la línea de caché pertenece (modo exclusivo) al núcleo que realiza la modificación.

Por lo tanto, tiene razón, std::shared_ptr<T> const& se puede usar como una mejora de rendimiento en comparación con std::shared_ptr<T> .

También tiene razón en que existe un riesgo teórico para que el puntero / referencia se convierta en colgado debido a algunos aliasing.

Dicho esto, el riesgo ya está latente en cualquier programa de C ++: cualquier uso individual de un puntero o referencia es un riesgo. Yo diría que las pocas ocurrencias de std::shared_ptr<T> const& deberían ser una gota en el agua en comparación con el número total de usos de T& , T const& , T* , ...

Por último, me gustaría señalar que pasar a shared_ptr<T> const& es raro . Los siguientes casos son comunes:

  • shared_ptr<T> : necesito una copia de shared_ptr
  • T* / T const& / T& / T const& : Necesito un manejador (posiblemente nulo) para T

El siguiente caso es mucho menos común:

  • shared_ptr<T>& : Puedo volver a colocar el shared_ptr

Pero pasando shared_ptr<T> const& ? Los usos legítimos son muy raros.

Pasar shared_ptr<T> const& donde todo lo que desea es una referencia a T es un antipatrón: ¡obliga al usuario a utilizar shared_ptr cuando podría estar asignando T otra manera! La mayoría de las veces (99,99 ..%), no debe importar cómo se asigna T

El único caso en el que pasaría un shared_ptr<T> const& is si no está seguro de si necesitará una copia o no, y porque ha perfilado el programa y demostrado que este incremento / decremento atómico fue un cuello de botella que decidió diferir la creación de la copia solo en los casos en que sea necesario.

Este es un caso tan extremo que cualquier uso de shared_ptr<T> const& debe verse con el mayor grado de sospecha.


Creo que es perfectamente razonable pasar por const & si la función de destino es síncrona y solo hace uso del parámetro durante la ejecución, y no lo necesita más al regresar. En este caso, es razonable ahorrar en el costo de aumentar el recuento de referencias, ya que realmente no necesita seguridad adicional en estas circunstancias limitadas, siempre que comprenda las implicaciones y esté seguro de que el código es seguro.

Esto es diferente a cuando la función necesita guardar el parámetro (por ejemplo, en un miembro de la clase) para una referencia posterior.


En primer lugar, hay una diferencia semántica entre los dos: pasar el puntero compartido por valor indica que su función tomará parte de la propiedad del objeto subyacente. Pasar shared_ptr como referencia constante no indica ningún intento además de pasar el objeto subyacente por referencia constante (o puntero en bruto) aparte de obligar a los usuarios de esta función a usar shared_ptr. Así que sobre todo la basura.

La comparación de las implicaciones de rendimiento de los mismos es irrelevante siempre que sean semánticamente diferentes.

de https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/

No pase un puntero inteligente como parámetro de función a menos que desee utilizar o manipular el puntero inteligente, como compartir o transferir la propiedad.

y esta vez estoy totalmente de acuerdo con Herb :)

Y otra cita del mismo, que responde la pregunta más directamente.

Pauta: use un parámetro shared_ptr & no solo constante para modificar el shared_ptr. Use una variable shared_ptr y como parámetro solo si no está seguro de si tomará o no una copia y compartirá la propiedad; de lo contrario, utilice * en su lugar (o si no es anulable, un &)


En primer lugar, no pase un shared_ptr por una cadena de llamadas a menos que exista la posibilidad de que una de las funciones llamadas almacene una copia de la misma. Pase una referencia al objeto referido, o un puntero en bruto a ese objeto, o posiblemente un cuadro, dependiendo de si puede ser opcional o no.

Pero cuando pasa un shared_ptr , entonces preferiblemente pase por referencia a const , porque copiar un shared_ptr tiene una sobrecarga adicional. La copia debe actualizar el recuento de referencia compartido, y esta actualización debe ser segura para subprocesos. Por lo tanto, hay una pequeña ineficiencia que se puede evitar (de manera segura).

Respecto a

" El precio hace que el código sea menos seguro, ¿verdad?

No. El precio es una indirección adicional en el código de máquina generado de forma ingenua, pero el compilador lo maneja. De modo que se trata solo de evitar una sobrecarga menor pero totalmente innecesaria que el compilador no puede optimizar para usted, a menos que sea súper inteligente.

Como David Schwarz ejemplificó en su respuesta, cuando pasa por referencia al problema de aliasing , donde la función que llama a su vez cambia o llama a una función que cambia el objeto original, es posible. Y, según la ley de Murphy, ocurrirá en el momento más inconveniente, al costo máximo y con el código impenetrable más complejo. Pero esto es así independientemente de si el argumento es una string o un shared_ptr o lo que sea. Felizmente es un problema muy raro. Pero shared_ptr en cuenta, también para pasar shared_ptr instancias shared_ptr .


La ventaja de pasar el shared_ptr by const& es que el recuento de referencias no tiene que aumentarse y luego disminuirse. Debido a que estas operaciones deben ser seguras para subprocesos, pueden ser costosas.

Usted tiene toda la razón de que existe el riesgo de que pueda tener una cadena de pases por referencia que luego invalide el jefe de la cadena. Esto me sucedió una vez en un proyecto del mundo real con consecuencias en el mundo real. Una función encontró un shared_ptr en un contenedor y le pasó una referencia a él en una pila de llamadas. Una función en lo profundo de la pila de llamadas eliminó el objeto del contenedor, haciendo que todas las referencias se refirieran repentinamente a un objeto que ya no existía.

Entonces, cuando pasa algo por referencia, la persona que llama debe asegurarse de que sobreviva durante la vida útil de la llamada de función. No utilice un pase por referencia si esto es un problema.

(Supongo que tiene un caso de uso en el que hay una razón específica para pasar por shared_ptr lugar de por referencia. La razón más común es que la función llamada puede necesitar extender la vida útil del objeto).

Actualización : algunos detalles más sobre el error para los interesados: este programa tenía objetos que se compartieron e implementaron la seguridad de subprocesos internos. Fueron retenidos en contenedores y era común que las funciones extendieran sus vidas.

Este tipo particular de objeto podría vivir en dos contenedores. Uno cuando estaba activo y otro cuando estaba inactivo. Algunas operaciones funcionaron en objetos activos, algunas en objetos inactivos. El caso de error se produjo cuando se recibió un comando en un objeto inactivo que lo hizo activo, mientras que el contenedor de objetos inactivos shared_ptr el único shared_ptr para el objeto.

El objeto inactivo estaba ubicado en su contenedor. Se pasó una referencia a shared_ptr en el contenedor, por referencia, al controlador de comandos. A través de una cadena de referencias, shared_ptr llegó al código que se dio cuenta de que era un objeto inactivo que debía shared_ptr . El objeto se eliminó del contenedor inactivo (que destruyó el contenedor inactivo shared_ptr ) y se agregó al contenedor activo (que agregó otra referencia al shared_ptr pasado a la rutina "agregar").

En este punto, era posible que el único shared_ptr para el objeto que existía fuera el del contenedor inactivo. Cada otra función en la pila de llamadas solo tenía una referencia a ella. Cuando el objeto se eliminó del contenedor inactivo, el objeto podría destruirse y todas esas referencias eran para un shared_ptr que ya no existía.

Tomó alrededor de un mes para desenredar esto.


Si su método no implica ninguna modificación de la propiedad, no hay ningún beneficio para su método de tomar shared_ptr por copia o por referencia constante, contamina la API y podría incurrir en gastos generales (si se pasa por copia)

La forma limpia es pasar el tipo subyacente por referencia o referencia según su caso de uso

void doSomething(const T& o) {} auto s = std::make_shared<T>(...); // ... doSomething(*s);

El puntero subyacente no puede liberarse durante la llamada al método