La ventaja de usar std::unique_ptr<T>
(además de no tener que recordar llamar delete
o delete[]
explícitamente) es que garantiza que un puntero es 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 DEBE usar punteros inteligentes para administrar la vida útil de los objetos asignados dinámicamente.
Ahora, su problema es en realidad cómo usar esto con su código anterior .
Mi sugerencia es que si no desea transferir o compartir la propiedad, siempre debe pasar referencias al objeto. Declare su función de esta manera (con o sin const
calificadores, según sea necesario):
bool func(BaseClass& ref, int other_arg) { ... }
Luego, la persona que llama, que tiene un std::shared_ptr<BaseClass> ptr
, manejará el nullptr
caso o le pedirá bool func(...)
que calcule el resultado:
if (ptr) {
result = func(*ptr, some_int);
} else {
}
Esto significa que cualquier llamador debe prometer que la referencia es válida y que seguirá siendo válida durante la ejecución del cuerpo de la función.
Aquí está la razón por la que creo firmemente que debe no pasar 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 desreferenciable y puede 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 (desreferenciarlo con suerte hará que su programa se bloquee). ( el feo )
- La dirección de un bloque de memoria que se puede desreferenciar pero que no contiene lo que esperabas. Tal vez el puntero se modificó accidentalmente y ahora apunta a otra dirección de escritura (de una variable completamente diferente dentro de su proceso). Escribir en esta ubicación de memoria hará que suceda mucha diversión, a veces, durante la ejecución, porque el sistema operativo no se quejará siempre que se le permita escribir allí. ( ¡Zoinks! )
El uso correcto de punteros inteligentes alivia los casos bastante aterradores 3 y 4, que generalmente no son detectables en tiempo de compilación y que generalmente solo experimenta en tiempo de ejecución cuando su programa falla o hace cosas inesperadas.
Pasar punteros inteligentes como argumentos tiene dos desventajas: no se puede cambiar el carácter const
del objeto señalado sin hacer una copia (lo que agrega sobrecarga shared_ptr
y no es posible unique_ptr
), y aún queda con el segundo ( nullptr
) significado.
Marqué el segundo caso como ( el malo ) desde una perspectiva de diseño. Este es un argumento más sutil sobre la responsabilidad.
Imagínese lo que significa cuando una función recibe a nullptr
como parámetro. Primero tiene que decidir qué hacer con él: ¿usar un valor "mágico" en lugar del objeto perdido? cambiar el comportamiento por completo y calcular otra cosa (que no requiere el objeto)? entrar en pánico y lanzar una excepción? Además, ¿qué sucede cuando la función toma 2, 3 o incluso más argumentos por puntero sin formato? Tiene que comprobar cada uno de ellos y adaptar su comportamiento en consecuencia. Esto agrega un nivel completamente nuevo a la validación de entrada sin una razón real.
La persona que llama debe ser la que tenga suficiente información contextual para tomar estas decisiones, o, en otras palabras, lo malo es menos aterrador cuanto más sepa. La función, por otro lado, solo debe aceptar la promesa de la persona que llama de que la memoria a la que se apunta es segura para trabajar según lo previsto. (Las referencias siguen siendo direcciones de memoria, pero representan conceptualmente una promesa de validez).
std::unique_ptr
parastd::vector<std::unique_ptr>
discutir?