El objetivo de una shared_ptr
instancia distinta es garantizar (en la medida de lo posible) que mientras shared_ptr
esté dentro del alcance, el objeto al que apunta seguirá existiendo, porque su recuento de referencias será al menos 1.
Class::only_work_with_sp(boost::shared_ptr<foo> sp)
{
// sp points to an object that cannot be destroyed during this function
}
Entonces, al usar una referencia a a shared_ptr
, deshabilita esa garantía. Entonces, en tu segundo caso:
Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here
{
...
sp->do_something();
...
}
¿Cómo sabe que sp->do_something()
no explotará debido a un puntero nulo?
Todo depende de lo que haya en esas secciones '...' del código. ¿Qué pasa si llama a algo durante el primer '...' que tiene el efecto secundario (en algún lugar de otra parte del código) de borrar un shared_ptr
a ese mismo objeto? ¿Y si resulta ser el único que queda distinto shared_ptr
a ese objeto? Adiós objeto, justo donde estás a punto de intentar usarlo.
Entonces, hay dos formas de responder a esa pregunta:
Examine la fuente de todo su programa con mucho cuidado hasta que esté seguro de que el objeto no morirá durante el cuerpo de la función.
Vuelva a cambiar el parámetro para que sea un objeto distinto en lugar de una referencia.
Un consejo general que se aplica aquí: no se moleste en realizar cambios arriesgados en su código por el bien del rendimiento hasta que haya cronometrado su producto en una situación realista en un generador de perfiles y haya medido de manera concluyente que el cambio que desea realizar hará un Diferencia significativa en el rendimiento.
Actualización para el comentarista JQ
Aquí hay un ejemplo artificial. Es deliberadamente simple, por lo que el error será obvio. En ejemplos reales, el error no es tan obvio porque está oculto en capas de detalles reales.
Tenemos una función que enviará un mensaje a alguna parte. Puede ser un mensaje grande, así que en lugar de usar un mensaje std::string
que probablemente se copie a medida que se transmite a varios lugares, usamos shared_ptr
una cadena de caracteres:
void send_message(std::shared_ptr<std::string> msg)
{
std::cout << (*msg.get()) << std::endl;
}
(Simplemente lo "enviamos" a la consola para este ejemplo).
Ahora queremos agregar una función para recordar el mensaje anterior. Queremos el siguiente comportamiento: debe existir una variable que contenga el mensaje enviado más recientemente, pero mientras se está enviando un mensaje, no debe haber ningún mensaje anterior (la variable debe restablecerse antes de enviar). Entonces declaramos la nueva variable:
std::shared_ptr<std::string> previous_message;
Luego modificamos nuestra función de acuerdo con las reglas que especificamos:
void send_message(std::shared_ptr<std::string> msg)
{
previous_message = 0;
std::cout << *msg << std::endl;
previous_message = msg;
}
Entonces, antes de comenzar a enviar, descartamos el mensaje anterior actual, y luego, una vez que se completa el envío, podemos almacenar el nuevo mensaje anterior. Todo bien. Aquí hay un código de prueba:
send_message(std::shared_ptr<std::string>(new std::string("Hi")));
send_message(previous_message);
Y como era de esperar, esto se imprime Hi!
dos veces.
Ahora llega el Sr. Maintainer, que mira el código y piensa: Oye, ese parámetro send_message
es un shared_ptr
:
void send_message(std::shared_ptr<std::string> msg)
Obviamente, eso se puede cambiar a:
void send_message(const std::shared_ptr<std::string> &msg)
¡Piense en la mejora del rendimiento que esto traerá! (No importa que estemos a punto de enviar un mensaje generalmente grande a través de algún canal, por lo que la mejora del rendimiento será tan pequeña que no podrá medirse).
Pero el problema real es que ahora el código de prueba exhibirá un comportamiento indefinido (en las compilaciones de depuración de Visual C ++ 2010, se bloquea).
El Sr. Maintainer está sorprendido por esto, pero agrega un control defensivo send_message
en un intento de evitar que ocurra el problema:
void send_message(const std::shared_ptr<std::string> &msg)
{
if (msg == 0)
return;
Pero, por supuesto, sigue adelante y se bloquea, porque msg
nunca es nulo cuando send_message
se llama.
Como digo, con todo el código tan cerca en un ejemplo trivial, es fácil encontrar el error. Pero en programas reales, con relaciones más complejas entre objetos mutables que tienen punteros entre sí, es fácil cometer el error y difícil construir los casos de prueba necesarios para detectar el error.
La solución fácil, en la que desea que una función pueda depender de que shared_ptr
continúe siendo no nula en todo momento, es que la función asigne su propio verdadero shared_ptr
, en lugar de depender de una referencia a un existente shared_ptr
.
La desventaja es que la copia de a shared_ptr
no es gratuita: incluso las implementaciones "sin bloqueo" tienen que usar una operación entrelazada para respetar las garantías de subprocesamiento. Por lo tanto, puede haber situaciones en las que un programa pueda acelerarse significativamente cambiando a shared_ptr
en a shared_ptr &
. Pero este no es un cambio que se pueda realizar de forma segura en todos los programas. Cambia el significado lógico del programa.
Tenga en cuenta que se produciría un error similar si usáramos en std::string
todo en lugar de std::shared_ptr<std::string>
y en lugar de:
previous_message = 0;
para aclarar el mensaje, dijimos:
previous_message.clear();
Entonces el síntoma sería el envío accidental de un mensaje vacío, en lugar de un comportamiento indefinido. El costo de una copia adicional de una cadena muy grande puede ser mucho más significativo que el costo de copiar una shared_ptr
, por lo que la compensación puede ser diferente.