2012-06-30 12 views
54

Tengo mi primer intento de usar C++ 11 unique_ptr; Estoy reemplazando un puntero polimérico en bruto dentro de un proyecto mío, que pertenece a una clase, pero que se transmite con bastante frecuencia.¿Cómo pasar std :: unique_ptr around?

que 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 iba a ser capaz de cambiar a:

bool func(std::unique_ptr<BaseClass> ptr, int other_arg); 

Debido a que la persona que llama tiene que manejar el puntero de la propiedad a la función, lo que no quiero. Entonces, ¿cuál es la mejor solución para mi problema?

pensé en hacer 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 parece no instintiva a pasar algo ya escrito como _ptr como referencia, lo que haría ser 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ía necesario dos indirectos de puntero consecutivos para llegar a mi variable.

Respuesta

69

Si desea que la función utilice el punto, pase una referencia al mismo. No hay ninguna razón para atar la función para funcionar sólo con algún tipo de puntero inteligente:

bool func(BaseClass& base, int other_arg); 

Y en el uso del sitio llamada operator*:

func(*some_unique_ptr, 42); 

Alternativamente, si se permite que el argumento base ser nula , mantener a la firma como es, y utilizar la función de miembro de get():

bool func(BaseClass* base, int other_arg); 
func(some_unique_ptr.get(), 42); 
+1

Esto es bueno, pero cam se deshace de 'std :: unique_ptr' para un argumento' std :: vector '? –

17

Estoy de acuerdo con Martinho, pero yo creo que es importante t o señalar la semántica de propiedad de una referencia de paso a paso. Creo que la solución correcta es utilizar una simple referencia pase por aquí:

bool func(BaseClass& base, int other_arg); 

El significado comúnmente aceptado de un pase por referencia en C++ es como como si la persona que llama de la función indica a la función " aquí, puede tomar prestado este objeto, usarlo y modificarlo (si no es const), pero solo mientras dure el cuerpo de la función ". Esto de ninguna manera está en conflicto con las reglas de propiedad del unique_ptr porque el objeto simplemente se toma prestado por un corto período de tiempo, no hay una transferencia de propiedad real (si prestas tu auto a alguien, ¿firmas el título a él?).

Así que, aunque podría parecer malo (diseño, prácticas de codificación, etc.) extraer la referencia (o incluso el puntero sin formato) del unique_ptr, en realidad no es porque esté perfectamente de acuerdo con las reglas de propiedad establecidas por unique_ptr. Y luego, por supuesto, hay otras bonitas ventajas, como la sintaxis limpia, no hay restricción para solo los objetos propiedad de un unique_ptr, y así sucesivamente.

17

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:

  1. La dirección de un bloque de memoria donde se encuentra el objeto deseado. (el bueno)
  2. 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)
  3. 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)
  4. 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.)

0

Personalmente, evito extraer una referencia de un puntero/puntero inteligente. Porque, ¿qué ocurre si el puntero es nullptr? Si cambia la firma a esto:

bool func(BaseClass& base, int other_arg); 

Puede que tenga que proteger el código de desreferencias puntero nulo:

if (the_unique_ptr) 
    func(*the_unique_ptr, 10); 

Si la clase es el único propietario del puntero, el segundo de la alternativa de Martinho parece más razonable:

func(the_unique_ptr.get(), 10); 

Como alternativa, puede utilizar std::shared_ptr. Sin embargo, si hay una única entidad responsable de delete, la sobrecarga std::shared_ptr no da resultado.

+0

¿Sabía que, en la mayoría de las ocasiones, 'std :: unique_ptr' no tiene gastos generales, ¿verdad? – lvella

+0

Sí, soy consciente de ello. Es por eso que le conté sobre la sobrecarga 'std :: shared_ptr'. –

Cuestiones relacionadas