c++ - smart - ¿Debo pasar un shared_ptr por referencia?
unique pointer (3)
En circunstancias controladas, puede pasar el puntero compartido por referencia constante . Asegúrese de que nadie esté eliminando el objeto al mismo tiempo, aunque esto no debería ser demasiado difícil si tiene cuidado con a quién le da referencias.
En general, debe pasar el puntero compartido como una copia directa. Esto le da su semántica prevista: cada ámbito que contiene una copia del puntero compartido mantiene vivo el objeto en virtud de su "compartir" en la propiedad.
La única razón para no pasar siempre por el valor es que la copia de un puntero compartido tiene un precio determinado a causa de la actualización del conteo de referencia atómica; sin embargo, esto podría no ser una gran preocupación.
Digresión opcional:
Dado que la pregunta principal ha sido respondida, tal vez es instructivo considerar algunas formas en las que nunca debe usar un puntero compartido. Aquí hay un pequeño experimento mental. Definamos un tipo de puntero compartido SF = std::shared_ptr<Foo>
. Para considerar las referencias, en lugar de pasar argumentos de funciones, veamos el tipo RSF = std::reference_wrapper<T>
. Es decir, si tenemos un puntero compartido SF p(std::make_shared<Foo>());
, entonces podemos hacer un contenedor de referencia con semántica de valores a través de RSF w = std::ref(p);
. Demasiado para la configuración.
Ahora, todo el mundo sabe que los contenedores de punteros son campo minado. Entonces std::vector<Foo*>
será una pesadilla para mantener, y cualquier cantidad de errores surge de un manejo inadecuado de por vida. Lo que es peor conceptualmente es que nunca está claro a quién pertenecen los objetos cuyos punteros almacena el contenedor. Los punteros incluso podrían ser una combinación de punteros a objetos dinámicos, objetos automáticos y basura. Nadie puede decirlo. Entonces la solución estándar es usar std::vector<SF>
lugar. Esta es la forma correcta de usar el puntero compartido. Por otro lado, lo que nunca debes usar es std::vector<RSF>
: ¡este es un monstruo inmanejable que en realidad es muy similar al vector original de punteros desnudos! Por ejemplo, no está claro si el objeto al que tiene referencia sigue vivo. Tomar una referencia del puntero compartido ha frustrado todo su propósito.
Para un segundo ejemplo, supongamos que tenemos un puntero compartido SF p
como antes. Ahora tenemos una función int foo(SF)
que queremos ejecutar al mismo tiempo. El estándar std::thread(foo, p)
funciona bien, ya que el constructor de hilos hace una copia de sus argumentos. Sin embargo, si dijéramos std::thread(foo, std::ref(p))
, estaríamos en todo tipo de problemas: el puntero compartido en el alcance de la llamada podría caducar y destruir el objeto, y usted quedaría con una referencia colgante y un puntero no válido!
Espero que estos dos ejemplos ciertamente inventados arrojen un poco de luz cuando realmente quieres que tus punteros compartidos se transmitan por copia . En un programa bien diseñado, siempre debe estar claro quién es responsable de qué recursos, y si se usa correctamente, el puntero compartido es una gran herramienta para el trabajo.
Esta pregunta ya tiene una respuesta aquí:
¿Cuáles son las mejores prácticas para pasar un shared_ptr?
Actualmente paso los argumentos de la función shared_ptr así:
void function1( shared_ptr<TYPE>& value );
Eso depende de lo que quieras. ¿Debería el callee compartir la propiedad del objeto? Luego necesita su propia copia de shared_ptr
. Así que pásalo por valor.
Si una función simplemente necesita acceder a un objeto propiedad de la persona que llama, continúe y pase por (const) referencia, para evitar la sobrecarga de copiar el shared_ptr
.
La mejor práctica en C ++ es siempre tener una semántica de propiedad claramente definida para sus objetos. No hay universal "siempre haga esto" para reemplazar el pensamiento real.
Si siempre pasa punteros compartidos por valor, se vuelve costoso (porque son mucho más caros de copiar que un puntero sin formato). Si nunca lo haces, no tiene sentido utilizar un puntero compartido en primer lugar.
Copie el puntero compartido cuando una nueva función u objeto necesite compartir la propiedad de la punta.
Pásalo por const reference si pasas por referencia. Eso deja en claro que estás pasando por ref por razones de rendimiento. Además, use make_shared cuando pueda, ya que guarda una indirección, por lo que le da un impulso de perf.