Pregunta

Cuando se utiliza el pImpl idiom es preferible utilizar un boost:shared_ptr en lugar de un 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] ¿Siempre es seguro usar std :: auto_ptr < > ¿O hay situaciones en las que se requiere un puntero inteligente de impulso alternativo?

¿Fue útil?

Solución

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

Si usa boost :: scoped_ptr ( no necesita shared_ptr aquí, no compartirá el pimpl con ningún otro objeto, y esto se aplica mediante scoped_ptr siendo noncopyable ), solo necesita el destructor pimpl visible en el punto que llama al constructor scoped_ptr.

Por ejemplo

// 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 instancia el 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 tiene este problema, porque cuando llama al constructor de un scoped_ptr (o el método de reinicio) también hace un equivalente de puntero de función que usará en lugar de llamar a delete . 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 le permite especificar una desasignación personalizada , por lo que puede usarla para cosas como identificadores GDI o cualquier otra cosa que desee, pero eso es excesivo para sus necesidades aquí.

Si realmente desea usar std :: auto_ptr, debe tener mucho cuidado al asegurarse de definir su 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 destruye todos los miembros de MyClass efectivamente 'en' el destructor vacío. Entonces, en el momento en que se instancia el destructor auto_ptr, Pimpl ya no está incompleto y el compilador ahora sabe cómo llamar al destructor.

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

Otros consejos

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

Hay poco para elegir entre <=> & amp; los punteros de impulso. Tiendo a no usar el impulso por motivos estilísticos si una alternativa de biblioteca estándar sirve.

boost :: shared_ptr está especialmente diseñado para funcionar con modismo 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.

Si usted es realmente pedante, no hay garantía absoluta de que usar un miembro auto_ptr no requiera una definición completa del parámetro de plantilla const auto_ptr en el punto en el que se usa. Dicho esto, nunca he visto que esto no funcione.

Una variación es usar un <=>. Esto funciona siempre que pueda construir su 'pimpl' con una nueva expresión dentro de la lista de inicializadores y garantiza que el compilador no puede generar un constructor de copia predeterminado y métodos de asignación. Todavía 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.

Si desea una clase que se pueda copiar, use scoped_ptr, que prohíbe la copia, lo que hace que su clase sea difícil de usar de manera predeterminada (en comparación con el uso de shared_ptr, el compilador no emitirá recursos de copia por sí mismo; y en el caso de <=>, si no sabe lo que hace [que suele ser 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 define un constructor de copias y una asignación de copias:

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

...
CopyableFoo (const CopyableFoo& rhs)
    : impl_(new Impl (*rhs.impl_))
{}

shared_ptr es mucho más preferible que auto_ptr para pImpl porque su clase externa podría terminar perdiendo su puntero cuando la copie.

Con shared_ptr puede usar un tipo declarado hacia adelante para que funcione. auto_ptr no permite un tipo declarado hacia adelante. Scoped_ptr tampoco lo hace, y si su clase externa no será copiable de todos modos y solo tiene un puntero, también podría ser uno regular.

Hay mucho que decir para usar un recuento de referencias intrusivas en el pImpl y hacer que la clase externa llame a su copia y asigne semántica en su implementación. Suponiendo que se trata de un modelo de verdadero proveedor (proporciona la clase), es mejor que el proveedor no obligue al usuario a utilizar shared_ptr, ni a utilizar la misma versión de shared_ptr (boost o std).

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

Modifiqué el código original, por lo que ahora se parece a shared_ptr, por lo que se puede usar en código epílogo y se mantiene rápido.

No te esfuerces tanto en dispararte en el pie, en C ++ tienes muchas oportunidades :) No hay una necesidad real de usar punteros automáticos, ya que sabe perfectamente cuándo su objeto debe entrar y salir de la vida (en su constructor (es) y destructor).

Mantenlo simple.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top