Estoy teniendo mi primer intento de usar C ++ 11 unique_ptr
; Estoy reemplazando un puntero crudo polimórfico dentro de un proyecto mío, que es propiedad de una clase, pero que se transmite con bastante frecuencia.
Solía tener funciones como:
bool func(BaseClass* ptr, int other_arg) {
bool val;
// plain ordinary function that does something...
return val;
}
Pero pronto me di cuenta de que no podría cambiar a:
bool func(std::unique_ptr<BaseClass> ptr, int other_arg);
Porque la persona que llama tendría que manejar la propiedad del puntero a la función, lo que no quiero. Entonces, ¿cuál es la mejor solución a mi problema?
Pensé en pasar el puntero como referencia, así:
bool func(const std::unique_ptr<BaseClass>& ptr, int other_arg);
Pero me siento muy incómodo al hacerlo, en primer lugar porque no parece instintivo pasar algo ya escrito _ptr
como referencia, lo que sería una referencia de una referencia. En segundo lugar, porque la firma de la función se hace aún más grande. En tercer lugar, porque en el código generado, serían necesarias dos indirecciones de puntero consecutivas para llegar a mi variable.
fuente
std::unique_ptr
parastd::vector<std::unique_ptr>
discutir?La ventaja de usar
std::unique_ptr<T>
(además de no tener que recordar llamardelete
odelete[]
explícitamente) es que garantiza que un puntero esnullptr
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á elnullptr
caso o le pedirábool func(...)
que calcule 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 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:
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 sobrecargashared_ptr
y no es posibleunique_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).
fuente
Estoy de acuerdo con Martinho, pero creo que es importante señalar la semántica de propiedad de un pase por referencia. Creo que la solución correcta es usar una simple pasada por referencia aquí:
bool func(BaseClass& base, int other_arg);
El significado comúnmente aceptado de un paso por referencia en C ++ es como si el llamador de la función le dijera a la función "aquí, puede tomar prestado este objeto, usarlo y modificarlo (si no es constante), pero solo para el duración de la función del cuerpo ". Esto, de ninguna manera, está en conflicto con las reglas de propiedad del
unique_ptr
porque el objeto simplemente está siendo prestado por un corto período de tiempo, no se está produciendo una transferencia de propiedad real (si presta su automóvil a alguien, ¿firma el título a él?).Entonces, aunque pueda parecer malo (en cuanto al diseño, prácticas de codificación, etc.) sacar la referencia (o incluso el puntero en bruto) fuera de la
unique_ptr
, en realidad no lo es porque está perfectamente de acuerdo con las reglas de propiedad establecidas por elunique_ptr
. Y luego, por supuesto, hay otras ventajas interesantes, como una sintaxis limpia, sin restricciones a solo objetos propiedad de aunique_ptr
, y así.fuente
Personalmente, evito sacar una referencia de un puntero / puntero inteligente. Porque, ¿qué pasa si el puntero es
nullptr
? Si cambia la firma a esta:bool func(BaseClass& base, int other_arg);
Es posible que deba proteger su código de las desreferencias de puntero nulo:
if (the_unique_ptr) func(*the_unique_ptr, 10);
Si la clase es la única dueña del puntero, la segunda alternativa de Martinho parece más razonable:
func(the_unique_ptr.get(), 10);
Alternativamente, puede usar
std::shared_ptr
. Sin embargo, si hay una sola entidad responsabledelete
, losstd::shared_ptr
gastos generales no se amortizan.fuente
std::unique_ptr
tiene cero gastos generales, verdad?std::shared_ptr
gastos generales.