2012-01-03 14 views
47

He escrito un método de fábrica estático que devuelve un nuevo objeto Foobar poblado de otro objeto de datos. Recientemente he estado obsesionado con la semántica de la propiedad y me pregunto si estoy transmitiendo el mensaje correcto al hacer que este método de fábrica devuelva un unique_ptr.¿Existe una mala práctica para devolver unique_ptr para el puntero sin formato como semántica de propiedad?

class Foobar { 
public: 
    static unique_ptr<Foobar> factory(DataObject data); 
} 

Mi intención es decirle al código del cliente que poseen el puntero. Sin un puntero inteligente, simplemente devolvería Foobar*. Sin embargo, me gustaría hacer que se elimine esta memoria para evitar posibles errores, por lo que unique_ptr parecía una solución adecuada. Si el cliente desea extender la vida útil del puntero, simplemente llama al .release() una vez que obtiene el unique_ptr.

Foobar* myFoo = Foobar::factory(data).release(); 

Mi pregunta viene en dos partes:

  1. ¿Este enfoque transmitir la semántica de propiedad correctos?
  2. ¿Es esto una "mala práctica" devolver unique_ptr en lugar de un puntero sin formato?
+0

usted está volviendo un unqiue_ptr decirle al cliente que poseen el puntero? Esto es exactamente lo contrario de lo que esperaría (ya que tienen que apropiarse explícitamente del puntero único). – jknupp

+1

Es posible que desee usar move-semántica en su lugar (si puede usar C++ 11). Con esto, le corresponde al usuario decidir cómo prolongar la vida útil del objeto creado por la fábrica. – evnu

+1

@evnu eso es algo que se hace automáticamente, ¿no? –

Respuesta

58

Devolver un std::unique_ptr de un método de fábrica está muy bien y debería ser una práctica recomendada. El mensaje que transmite es (IMO): Ahora usted es el único propietario de este objeto. Además, para su conveniencia, el objeto sabe cómo destruirse a sí mismo.

Creo que esto es mucho mejor que devolver un puntero sin formato (donde el cliente tiene que recordar cómo y si desechar este puntero).

Sin embargo, no entiendo su comentario sobre la liberación del puntero para extender su vida útil. En general, rara vez veo ningún motivo para llamar al release en un smartpointer, ya que creo que los punteros siempre deben estar gestionados por algún tipo de estructura RAII (casi la única situación en la que llamo release es poner el puntero en una estructura de gestión diferente, por ejemplo un unique_ptr con un eliminador diferente, después de que hice algo para garantizar una limpieza adicional).

Por lo tanto el cliente puede (y debe) simplemente almacenar el unique_ptr en alguna parte (tal como otro unique_ptr, que ha sido movida construido a partir de la devuelta), siempre y cuando que necesitan el objeto (o una shared_ptr, si necesitan varias copias del puntero).Así que el código del lado del cliente debe ser de la misma familia:

std::unique_ptr<FooBar> myFoo = Foobar::factory(data); 
//or: 
std::shared_ptr<FooBar> myFoo = Foobar::factory(data); 

Personalmente añadiría también un typedef para el tipo de puntero devuelto (en este caso std::unique_ptr<Foobar>) y la Deleter o usado (en este caso std :: default_deleter) a su objeto de fábrica. Eso hace que sea más fácil si luego decide cambiar la asignación de su puntero (y por lo tanto necesita un método diferente para la destrucción del puntero, que será visible como un segundo parámetro de plantilla de std::unique_ptr). Así que me gustaría hacer algo como esto:

class Foobar { 
public: 
    typedef std::default_deleter<Foobar>  deleter; 
    typedef std::unique_ptr<Foobar, deleter> unique_ptr; 

    static unique_ptr factory(DataObject data); 
} 

Foobar::unique_ptr myFoo = Foobar::factory(data); 
//or: 
std::shared_ptr<Foobar> myFoo = Foobar::factory(data); 
+3

No quise que mi mención de 'release()' sea engañosa o confusa, lo siento. Este nuevo código va a una aplicación existente bastante grande (1-2 millones de líneas de C++) que no utiliza ningún puntero inteligente (que no sea COM).Sé que otros en mi equipo estarán preocupados si no hay una manera razonable de llegar al puntero sin procesar si el código heredado lo hace efectivamente "requerido". Por supuesto, daré las advertencias adecuadas y recomendaré adherirme a unique_ptr/shared_ptr cuando sea posible. Me gusta mucho su idea typedef, que diferencia su respuesta de la de James McNellis. Gracias por las ideas. –

17

A std::unique_ptr posee el único objeto al que apunta. Dice "Poseo este objeto, y nadie más lo hace".

Eso es exactamente lo que estás tratando de expresar: estás diciendo "llamante de esta función: ahora eres el único propietario de este objeto; hazlo como quieras, su duración es tu responsabilidad".

+0

De hecho, pero ¿por qué no hacer que devuelva un 'std :: shared_pointer <>'? Logra el mismo efecto pero permite múltiples copias del puntero. –

+8

@ AndréCaron: ¿Por qué imponer el uso de 'std :: shared_ptr' si lo único que desea hacer es transferir la propiedad a la persona que llama? Permita que quien llama asigne el objeto a 'std :: shared_ptr' si lo desea (u otro tipo de puntero inteligente, si el que llama quiere). –

+7

@ André Caron: La razón es básicamente el rendimiento. La fábrica no puede saber si el cliente tendrá o no la funcionalidad agregada por 'std :: shared_ptr' sobre' std :: unique_ptr', entonces, ¿por qué sacrificar el rendimiento por alguna funcionalidad, que podría no ser necesaria? Dado que 'shared_ptr' se puede construir y asignar desde' unique_ptr', realmente no hay ningún beneficio – Grizzly

6

Transmite exactamente la semántica correcta y es la forma en que creo que todas las fábricas en C++ deberían funcionar: std::unique_ptr<T> no impone ningún tipo de semántica de propiedad y es extremadamente barato.

Cuestiones relacionadas