c++ boost stl shared-ptr auto-ptr

c++ - std:: auto_ptr o boost:: shared_ptr para pImpl idiom?



stl shared-ptr (9)

Al utilizar la expresión pImpl, es preferible utilizar un boost:shared_ptr lugar de std::auto_ptr ? Estoy seguro de que una vez leí que la versión de refuerzo es más amigable con las excepciones.

class Foo { public: Foo(); private: struct impl; std::auto_ptr<impl> impl_; }; class Foo { public: Foo(); private: struct impl; boost::shared_ptr<impl> impl_; };

[EDITAR] ¿Es seguro usar std :: auto_ptr <> o hay situaciones en las que se necesita un puntero inteligente de refuerzo alternativo?


La alternativa de impulso a std::auto_ptr es boost::scoped_ptr . La diferencia principal de auto_ptr es que boost::scoped_ptr se puede copiar.

Vea esta página para más detalles.


No intentes dispararte tan fuerte en el pie, en C ++ tienes muchas oportunidades :) No hay necesidad real de usar punteros automáticos, ya que sabes perfectamente cuándo debe entrar y salir tu objeto (en tu constructor (es) y destructor).

Mantenlo simple.


Si está siendo realmente pedante no hay una garantía absoluta de que el uso de un miembro auto_ptr no requiera una definición completa del parámetro de la plantilla auto_ptr en el punto en el que se usa. Habiendo dicho eso, nunca he visto que esto no funcione.

Una variación es usar un const auto_ptr . Esto funciona siempre que pueda construir su ''pimpl'' con una nueva expresión dentro de la lista de inicializadores y garantice que el compilador no puede generar métodos de asignación y constructor de copia predeterminados. Aún se debe proporcionar un destructor no en línea para la clase adjunta.

En igualdad de condiciones, preferiría una implementación que use solo las bibliotecas estándar, ya que mantiene las cosas más portátiles.


Tiendo a usar auto_ptr . Asegúrese de que su clase no se pueda copiar (declare private copy ctor & operator =, o bien herede boost::noncopyable ). Si usa auto_ptr , una de las arrugas es que necesita definir un destructor no en línea, incluso si el cuerpo está vacío. (Esto se debe a que si permite que el compilador genere el destructor predeterminado, impl será un tipo incompleto cuando se genere la llamada para delete impl_ , invocando un comportamiento indefinido).

Hay poco para elegir entre auto_ptr y los punteros de impulso. Tiendo a no utilizar el impulso en motivos estilísticos si una alternativa de biblioteca estándar va a hacer.


boost :: shared_ptr está especialmente diseñado para trabajar en idioma de pimpl. Una de las principales ventajas es que permite no definir el destructor para la clase que contiene pimpl. La política de propiedad compartida puede ser tanto una ventaja como una desventaja. Pero en el caso posterior puede definir el constructor de copia correctamente.


shared_ptr es mucho más preferible que auto_ptr for pImpl porque tu clase externa podría terminar perdiendo su puntero al copiarlo.

Con shared_ptr puedes usar un tipo declarado de manera progresiva para que funcione. auto_ptr no permite un tipo declarado a futuro. Tampoco scoped_ptr y si su clase externa no se puede copiar de todos modos y solo tiene un puntero, bien podría ser uno regular.

Hay mucho que decir sobre el uso de un recuento de referencias intrusivas en el pImpl y obtener la clase externa para llamar a su copia y asignar semántica en su implementación. Suponiendo que se trata de un modelo de proveedor real (suministra la clase), es mejor que el proveedor no obligue al usuario a usar shared_ptr, o que esté utilizando la misma versión de shared_ptr (boost o std).


Si desea una clase que se pueda copiar, use scoped_ptr , que prohíbe copiar, lo que hace que su clase sea difícil de usar de manera incorrecta (en comparación con el uso de shared_ptr , el compilador no emitirá recursos de copia por sí mismo, y en el caso de shared_ptr , si no sé lo que haces [que a menudo es suficiente el caso incluso para los asistentes], habría un comportamiento extraño cuando de repente una copia de algo también modifica ese algo), y luego definir un constructor de copias y una tarea de copia:

class CopyableFoo { public: ... CopyableFoo (const CopyableFoo&); CopyableFoo& operator= (const CopyableFoo&); private: scoped_ptr<Impl> impl_; }; ... CopyableFoo (const CopyableFoo& rhs) : impl_(new Impl (*rhs.impl_)) {}


En realidad, no deberías usar std :: auto_ptr para esto. El destructor no será visible en el momento en que declare std :: auto_ptr, por lo que podría no ser llamado correctamente. Esto supone que está declarando su clase pImpl y creando la instancia dentro del constructor en otro archivo.

Si usas boost :: scoped_ptr (no necesitas shared_ptr aquí, no compartirás el pimpl con ningún otro objeto, y esto es forzado por scoped_ptr al no poder copiarse ), solo necesitas el destructor de pimpl visible en el punto al que llamas scoped_ptr constructor.

P.ej

// in MyClass.h class Pimpl; class MyClass { private: std::auto_ptr<Pimpl> pimpl; public: MyClass(); }; // Body of these functions in MyClass.cpp

Aquí, el compilador generará el destructor de MyClass. Que debe llamar al destructor de auto_ptr. En el punto donde se crea una instancia del destructor auto_ptr, Pimpl es un tipo incompleto. Entonces, en el destructor auto_ptr cuando elimina el objeto Pimpl, no sabrá cómo llamar al destructor Pimpl.

boost :: scoped_ptr (y shared_ptr) no tienen este problema, porque cuando llama al constructor de un scoped_ptr (o el método de reinicio) también crea una función-puntero-equivalente que usará en lugar de llamar a eliminar. El punto clave aquí es que crea una instancia de la función de desasignación cuando Pimpl no es un tipo incompleto. Como nota al margen, shared_ptr te permite especificar una función de desasignación personalizada , para que puedas usarla para cosas como GDI Handles o cualquier otra cosa que desees, pero eso es demasiado para tus necesidades aquí.

Si realmente quieres usar std :: auto_ptr, entonces debes tener especial cuidado asegurándote de definir tu destructor MyClass en MyClass.cpp cuando Pimpl esté completamente definido.

// in MyClass.h class Pimpl; class MyClass { private: std::auto_ptr<Pimpl> pimpl; public: MyClass(); ~MyClass(); };

y

// in MyClass.cpp #include "Pimpl.h" MyClass::MyClass() : pimpl(new Pimpl(blah)) { } MyClass::~MyClass() { // this needs to be here, even when empty }

El compilador generará el código de destrucción de todos los miembros de MyClass efectivamente ''en'' el destructor vacío. Entonces, en el momento en que el destructor auto_ptr es instanciado, Pimpl ya no está incompleto y el compilador ahora sabe cómo llamar al destructor.

Personalmente, no creo que valga la molestia de asegurarnos de que todo sea correcto. También existe el riesgo de que alguien venga más tarde y arregle el código eliminando el destructor aparentemente redundante. Por lo tanto, es más seguro ir con boost :: scoped_ptr para este tipo de cosas.


He estado muy contento con impl_ptr por Vladimir Batov [modificado] . Hace que sea muy fácil crear un pImpl sin necesidad de hacer un explícito copy-constructor y un operador de asignación.

He modificado el código original, por lo que ahora se asemeja a un shared_ptr, por lo que se puede utilizar en el código epilog y sigue siendo rápido.