Pasar el ptr compartido de const y el ptr compartido como parámetro


He estado leyendo un buen número de 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& en lugar de una copia, así:

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

Versus

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

Sin embargo, ¿la segunda variante no derrota 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 reingreso o efectos secundarios) que const puntero se convierte en inválido. Una situación que el puntero compartido debería prevenir. Entiendo que const & ahorra algo de tiempo, ya que no hay copia involucrada y no hay bloqueo para administrar el recuento de referencias. Pero el precio está haciendo que el código sea menos seguro, ¿verdad?

Author: ks1322, 2016-06-03

6 answers

La ventaja de pasar el shared_ptr por const& es que el recuento de referencia no tiene que ser aumentado y luego disminuido. Debido a que estas operaciones tienen que ser seguras para los hilos, pueden ser costosas.

Tienes toda la razón en que existe el riesgo de que puedas tener una cadena de pases por referencia que más tarde invalida la cabeza de la cadena. Esto me pasó una vez en un proyecto del mundo real con consecuencias del mundo real. Una función encontró un shared_ptr en un contenedor y pasó una referencia a es 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.

Así que cuando pasa algo por referencia, el llamante debe asegurarse de que sobreviva durante la vida de la llamada a la función. No utilice un pase por referencia si esto es un problema.

(Asumo que tienes un caso de uso donde hay alguna razón específica para pasar por shared_ptr en lugar de por referencia. Los más comunes la razón sería que la función llamada puede necesitar extender la vida del objeto.)

Actualización : Algunos detalles más sobre el error para aquellos interesados: Este programa tenía objetos que se compartieron e implementaron seguridad interna del hilo. Se mantenían en contenedores y era común que las funciones prolongaran su vida útil.

Este tipo particular de objeto podría vivir en dos contenedores. Uno cuando estaba activo y otro cuando estaba inactivo. Algunas operaciones trabajaron en activo objetos, algunos en objetos inactivos. El caso de error ocurrió cuando se recibió un comando en un objeto inactivo que lo hizo activo mientras que el único shared_ptr del objeto estaba retenido por el contenedor de objetos inactivos.

El objeto inactivo estaba ubicado en su contenedor. Se pasó una referencia al shared_ptr en el contenedor, por referencia, al controlador de comandos. A través de una cadena de referencias, este shared_ptr finalmente llegó al código que se dio cuenta de que era un objeto inactivo que tenía que hacerse activo. El objeto se eliminó del contenedor inactivo (que destruyó el shared_ptr del contenedor inactivo) 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 al objeto que existía era el que estaba en el 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 ser destruido y todas esas referencias eran a un shared_ptr que ya no existía.

Tomó alrededor de un mes desenredar esto.

 21
Author: David Schwartz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-03 10:19:33

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 ella. Pase una referencia al objeto referido, o un puntero raw a ese objeto, o posiblemente un cuadro, dependiendo de si puede ser opcional o no.

Pero cuando se pasa un shared_ptr, entonces preferiblemente pasarlo por referencia a const, porque copiar un shared_ptr tiene sobrecarga adicional. La copia debe actualizar el recuento de referencias compartidas, y esta actualización debe ser hilo seguro. Por lo tanto, hay un poco de ineficiencia que se puede evitar (de forma segura).

Respecto a

" el precio está haciendo que el código sea menos seguro, ¿verdad?

No. El precio es una indirecta extra en código máquina generado ingenuamente, pero el compilador lo maneja. Así que se trata 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 ejemplificado en su respuesta, cuando pasa por referencia a const el 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, sucederá en el momento más inconveniente, al máximo costo y con el código impenetrable más complicado. Pero esto es así independientemente de si el argumento es un string o un shared_ptr o lo que sea. Afortunadamente es un problema muy raro. Pero tenlo en cuenta, también para pasar instancias shared_ptr.

 12
Author: Cheers and hth. - Alf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-03 10:45:07

En primer lugar hay una diferencia semántica entre los dos: pasar el puntero compartido por valor indica que su función va a tomar su parte de la propiedad del objeto subyacente. Pasar shared_ptr como referencia const no indica ninguna intent además de simplemente pasar el objeto subyacente por referencia const (o puntero raw) aparte de forzar a los usuarios de esta función a usar shared_ptr. Así que en su mayoría basura.

Comparar las implicaciones de rendimiento de estos es irrelevante siempre y cuando son 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 usar o manipular el puntero inteligente en sí, como para compartir o transferir la propiedad.

Y esta vez estoy totalmente de acuerdo con Herb:)

Y otra cita de la misma, que responde a la pregunta más directamente

Guía: Use a no-const shared_ptr & parámetro solo para modificar el shared_ptr. Use un const shared_ptr & como parámetro solo si no está seguro de si tomará o no una copia y compartirá la propiedad; de lo contrario use * en su lugar (o si no es nullable, un&)

 5
Author: Slava,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-03 14:01:10

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

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

 3
Author: Smeeheey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-03 09:31:41

Si no hay ninguna modificación de la propiedad involucrada en su método, no hay beneficio para su método para tomar un shared_ptr por copia o por referencia const, contamina la API y potencialmente incurrir en sobrecarga (si pasa por copia)

La manera limpia es pasar el tipo subyacente por const ref o ref dependiendo de su caso de uso

void doSomething(const T& o) {}

auto s = std::make_shared<T>(...);
// ...
doSomething(*s);

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

 3
Author: Stephane Molina,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-03 09:53:10

Como se señala en C++ - shared_ptr: horrible velocidad, copiar un 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 de memoria / tiendas no pueden migrar a través de la operación) y a nivel de hardware implica el protocolo de coherencia de caché de CPU para garantizar que toda la línea de caché modificación.

Así que, tienes razón, std::shared_ptr<T> const& se puede usar como una mejora de rendimiento sobre solo std::shared_ptr<T>.


También tiene razón en que existe un riesgo teórico de que el puntero/referencia se cuelgue debido a algún aliasing.

Dicho esto, el riesgo ya está latente en cualquier programa 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& deben ser una caída 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 un shared_ptr<T> const& es raro. Los siguientes casos son comunes:

  • shared_ptr<T>: Necesito una copia del shared_ptr
  • T*/T const&/T&/T const&: Necesito un identificador (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, muy raros.

Pasando shared_ptr<T> const& donde todo lo que quieres es una referencia a T es un anti-patrón: obligas al usuario a usar shared_ptr cuando podrían asignar T de otra manera! La mayoría de las veces (99,99..% ), no debería importarle cómo se asigna T.

El único caso en el que pasaría un shared_ptr<T> const& es 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 era un cuello de botella, ha decidido aplazar la creación de la copia a sólo en los casos en que sea necesario.

Este es un caso tan extremo que cualquier uso de shared_ptr<T> const& debe ser visto con el más alto grado de sospecha.

 2
Author: Matthieu M.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:17:33