2012-03-14 10 views
45

Estoy tratando de "modernizar" algún código existente.Pasando unique_ptr a las funciones

  • Tengo una clase que actualmente tiene una variable miembro "Device * device_".
  • Utiliza new para crear una instancia en algún código de inicialización y tiene un "dispositivo delete_" en el destructor.
  • Las funciones miembro de esta clase llaman al muchas otras funciones que toman un Dispositivo * como parámetro.

Esto funciona bien, pero a "modernizar" mi código pensé que debía cambiar la variable que se define como "std::unique_ptr<Device> device_" y retire la llamada explícita a eliminar, lo que hace el código más seguro y en general mejor.

Mi pregunta es la siguiente -

  • ¿Cómo debería entonces pasar el dispositivo de _ variable para todas las funciones que lo necesitan como un parámetro de?

Puedo llamar .get para obtener el puntero sin formato en cada llamada a la función. Pero eso parece feo y desperdicia algunas de las razones para usar un unique_ptr en primer lugar.

O puedo cambiar cada función de manera que en lugar de tomar un parámetro de tipo "Dispositivo *" ahora toma un parámetro de tipo "std :: unique_ptr &". Lo cual (para mí) ofusca un tanto los prototipos de funciones y los hace difíciles de leer.

¿Cuál es la mejor práctica para esto? ¿Me he perdido otras opciones?

+3

¿Por qué este cambio que el código sea más seguro y generalmente mejor? De su descripción, diría que todo lo contrario es verdad. –

+0

@James Kanze Estoy de acuerdo, unique_ptr no parece comprar nada en este caso. – juanchopanza

+2

Es posible que tenga razón. Me gusta la "seguridad" de unique_ptr en el sentido de que no tiene que acordarse de eliminarlo, pero parece que tiene algún costo en este caso. – jcoder

Respuesta

44

En moderno estilo C++, hay dos claves conceptos:

  • Propiedad
  • Nulidad

Propiedad es sobre el propietario de un objeto/recurso (en este caso , una instancia de Device). Los diversos std::unique_ptr, boost::scoped_ptr o std::shared_ptr son sobre la propiedad.

Nullity es mucho más simple: expresa si un objeto determinado puede ser nulo o no, y no le importa nada más, ¡y ciertamente no sobre la propiedad!


Eras derecha para mover la implementación de su clase hacia unique_ptr (en general), aunque es posible que desee un puntero inteligente con la semántica de copia profunda si su objetivo es implementar un PIMPL.

Esto transmite claramente que su clase es la única responsable de esta pieza de memoria y se ocupa de todas las formas en que la memoria podría haberse filtrado de otra manera.


Por otro lado, la mayoría usuarios de los recursos no podían cuidar menos sobre su propiedad.

Mientras una función no mantenga una referencia a un objeto (almacenarlo en un mapa o algo así), entonces lo único que importa es que la vida útil del objeto excede la duración de la llamada a la función.

Por lo tanto, la elección de la forma de pasar el parámetro depende de su posible nulidad :

  • Nunca nula? Pase una referencia
  • Posiblemente nulo?Pasar un puntero , un simple puntero desnudo o una clase de puntero similar (con una trampa en la nula por ejemplo)

+0

Hmm, referencias ... Podría hacer que mis funciones tomen los parámetros "Dispositivo y dispositivo" y luego en el código de llamada use * dispositivo_, ya que eso funcionará con unique_ptr y sé que device_ no puede ser nulo (y puede probar eso con afirmaciones). ¡Ahora necesito reflexionar un poco sobre eso y decidir si es feo o elegante! – jcoder

+2

@JohnB: bueno, mi punto de vista optimista es que erradicar la posibilidad de nulidad en el tiempo de compilación es mucho más seguro :) –

+6

@JohnB: Estoy completamente de acuerdo con Matthieu aquí, y yo iría incluso más allá, ¿por qué quieres dinámicamente? asignar un objeto cuya vida útil está vinculada por el tiempo de vida del objeto que contiene el puntero? ¿Por qué no declararlo como un miembro simple con almacenamiento automático? –

7

Yo usaría std::unique_ptr const&. El uso de una referencia no constante le dará a la función llamada la posibilidad de restablecer su puntero.
Creo que esta es una buena manera de expresar que su función llamada puede usar el puntero pero nada más.
Por lo tanto, para mí esto hará que la interfaz sea más fácil de leer. Sé que no tengo que jugar con el puntero que me han pasado.

+0

Entonces, para que quede claro, agregar const significa que no puedo cambiar el unique_ptr de ninguna manera, pero I * puede * llamar a funciones no const del objeto contenido a través del unique_ptr? – jcoder

+0

@JohnB: Sí, la función impide que la función llamada invoque, por ejemplo, restablecer en su unique_ptr. Pero aun así, mientras el puntero interno no sea const, puede llamar a funciones no const. – mkaes

+0

Sí, puedes. El método get() const del unique_ptr devuelve un puntero no const al objeto protegido por unique_ptr – zabulus

1

Eso puede no ser factible para usted, pero reemplazar cada ocurrencia de Device* por es un buen comienzo.

Obviamente no puede copiar unique_ptr s y no desea moverlo. Sustituir por una referencia a unique_ptr permite que el cuerpo de los cuerpos de las funciones existentes siga funcionando.

Ahora hay una trampa, debe pasar por const & para evitar que calle haga unique_ptr.reset() o unique_ptr().release(). Tenga en cuenta que esto todavía pasa un puntero modificable al dispositivo. Con esta solución, no tiene una manera fácil de pasar un puntero o referencia a un const Device.

+0

Como const unique_ptr & es difícil de manejar, escribiría eso en Device_t o similar. –

14

Realmente depende. Si una función debe tomar posesión de unique_ptr, entonces su firma debe tomar un unique_ptr<Device> bv valor y la persona que llama debe std::move el puntero. Si la propiedad no es un problema, entonces mantendría la firma del puntero sin procesar y pasaría el puntero unique_ptr usando get(). Esto no es feo si la función en cuestión no se hace cargo de la propiedad.

+0

Sí No deseo tomar ownweship, solo use el apuntado al objeto en la función. – jcoder

+2

También puede pasar una referencia constante al unique_ptr si la propiedad no es un problema. Algunas personas usan la dicotomía de referencia de puntero para simular la semántica de parámetros formales in-out de Pascals (guía de estilo de Google C++). –

2

La mejor práctica es probablemente no utilizar std::unique_ptr en este caso, aunque depende. (Por lo general, no debe tener más de un puntero sin formato a un objeto dinámicamente asignado en una clase. Aunque este también depende). Lo único que no desea hacer en este caso es pasando por std::unique_ptr (y como has notado, std::unique_ptr<> const& es un poco difícil de manejar y confuso). Si este es el único puntero asignado dinámicamente en el objeto, me limitaría a incluir con el puntero sin formato y delete en el destructor. Si hay varios de estos indicadores, consideraría relegar cada uno de ellos a una clase base separada (donde aún pueden ser punteros sin formato).

Cuestiones relacionadas