La ventaja de usar std::unique_ptr<T>
(aparte de no tener que recordar llamar delete
o delete[]
explícitamente) es que garantiza que un puntero es o bien nullptr
o apunta a una instancia válida del objeto (base).Volveré a esto después de responder a su pregunta, pero el primer mensaje es DO utilice punteros inteligentes para administrar la vida útil de los objetos asignados dinámicamente.
Ahora, el problema es en realidad cómo utilizar esto con el código antiguo.
Mi sugerencia es que si no desea transferir o compartir la propiedad, debe siempre pasar referencias al objeto. Declarar su función como esta (con o sin const
calificadores, según sea necesario):
bool func(BaseClass& ref, int other_arg) { ... }
Entonces la persona que llama, que tiene una std::shared_ptr<BaseClass> ptr
, o bien manejar el caso nullptr
o se le pedirá bool func(...)
para calcular el resultado:
if (ptr) {
result = func(*ptr, some_int);
} else {
/* the object was, for some reason, either not created or destroyed */
}
Esto significa que cualquier llamante tiene que promesa que la referencia es válida y que seguirá siendo válida durante la ejecución del cuerpo de la función.
Aquí es la razón por la que creo firmemente que debería no pase punteros primas o referencias a punteros inteligentes.
Un puntero sin formato es solo una dirección de memoria. Puede tener uno de (al menos) 4 significados:
- La dirección de un bloque de memoria donde se encuentra el objeto deseado. (el bueno)
- La dirección 0x0 de la que puede estar seguro no es dereferencable y podría tener la semántica de "nada" o "ningún objeto". (el malo)
- La dirección de un bloque de memoria que está fuera del espacio direccionable de su proceso (desreferenciando, con suerte hará que su programa falle). (el feo)
- La dirección de un bloque de memoria que puede ser desreferenciado pero que no contiene lo que usted espera. Tal vez el puntero fue modificado accidentalmente y ahora apunta a otra dirección grabable (de una variable completamente diferente dentro de su proceso). Escribir en esta ubicación de memoria causará mucha diversión, a veces, durante la ejecución, ya que el sistema operativo no se quejará siempre que se le permita escribir allí. (Zoinks!)
correctamente el uso de punteros inteligentes alivia los casos en lugar de miedo 3 y 4, que por lo general no son detectables en tiempo de compilación, y que por lo general sólo la experiencia en tiempo de ejecución cuando se bloquea el programa o hace cosas inesperadas.
pasar punteros inteligentes como argumentos tiene dos desventajas: no se puede cambiar el const
-ness del señalado objeto sin hacer una copia (que implica una sobrecarga de shared_ptr
y no es posible para unique_ptr
), y todavía les queda el segundo significado (nullptr
).
Marqué el segundo caso como (el malo) desde una perspectiva de diseño. Este es un argumento más sutil sobre la responsabilidad.
Imagine lo que significa cuando una función recibe un nullptr
como su parámetro. Primero tiene que decidir qué hacer con él: ¿usar un valor "mágico" en lugar del objeto perdido? cambiar completamente el comportamiento y calcular algo más (que no requiere el objeto)? pánico y lanzar una excepción? Además, ¿qué sucede cuando la función toma 2, o 3 o incluso más argumentos por puntero sin formato? Tiene que verificar cada uno de ellos y adaptar su comportamiento en consecuencia. Esto agrega un nivel completamente nuevo además de la validación de entrada sin ninguna razón real.
La persona que llama debe ser la que tenga suficiente información contextual para tomar estas decisiones, o, en otras palabras, la mala es menos aterradora cuanto más sepa. La función, por otro lado, debería tomar la promesa del que llama de que la memoria a la que apunta es segura para que funcione de la forma prevista. (Las referencias siguen siendo direcciones de memoria, pero conceptualmente representan una promesa de validez.)
Esto es bueno, pero cam se deshace de 'std :: unique_ptr' para un argumento' std :: vector '? –