2010-03-01 15 views
10

Quels types d'astuces peuvent être utilisés pour minimiser la charge de travail liée à l'implémentation de classes pImpl?Implémentation de pImpl avec un minimum de code

tête:

class Foo { 
    struct Impl; 
    boost::scoped_ptr<Impl> self; 
public: 
    Foo(int arg); 
    ~Foo(); 
    // Public member functions go here 
}; 

Mise en œuvre:

struct Foo::Impl { 
    Impl(int arg): something(arg) {} 
    // All data members and private functions go here 
}; 

Foo::Foo(int arg): self(new Impl(arg)) {} 
Foo::~Foo() {} 

// Foo's public functions go here (and they refer to data as self->something) 

Comment améliorer cela, en utilisant Boost, peut-être l'héritage, CRTP ou d'autres astuces pour éviter autant code boilerplate que possible? La performance d'exécution n'est pas un problème.

+3

En fait, cela ne met pas en œuvre l'idiome Pimpl soit parce que vous ne l'avez pas fixé le constructeur de copie et l'affectation de copie ' operator = 'dans Foo. –

+1

Je n'ai généralement pas besoin de copier (et scoped_ptr fournit par défaut une non-occlusion), mais vous avez un point, la création de ces fonctions pourrait également être automatisée. – Tronic

Répondre

5

La mise en œuvre de pimpl de Loki peut être une bonne réponse. Voir aussi un DDJ Article à ce sujet.

+0

Je suppose que c'est à peu près aussi bon que ça ... – Tronic

1

C'est possible, mais une implémentation naïve n'est pas ce que vous voulez.

Le problème est que les modèles sont généralement inline, la mise en œuvre naïve serait:

template <class Object> 
class Pimpl 
{ 
public: 
    explicit Pimpl(Object* obj): mObject(obj) {} 
    ~Pimpl() { delete mObject; } 

    // either deep copy or no copy 
private: 
    Object* mObject; 
}; 

Maintenant, le problème est que vous ne voulez pas Object à être connu dans votre fichier d'en-tête en général (pas binaire compatibilité, mais pour la gestion des dépendances). Et si Object on ne sait pas, alors vous ne pouvez pas mettre en œuvre la Destructor, Copy Constructor et Assignment Operator directement ...

Le problème est loin d'être impossible à résoudre cependant! Boost a en effet le résoudre pour le shared_ptr.

L'idée est de passer un deuxième élément dans le constructeur, qui prendra soin de libérer la mémoire du premier, et qui sera fourni avec une implémentation par défaut sympa.

Cela fonctionne avec une indirection, bien sûr.

namespace detail { 
    template <class Object> 
    struct Deleter { virtual void do(Object*) = 0; }; 
} 

template <class Object> 
class Pimpl 
{ 
public: 
    typedef detail::Deleter<Object> deleter_type; 
    typedef boost::shared_ptr<deleter_type> deleter_pointer; 

    Pimpl(std::auto_ptr<Object> obj, deleter_pointer del); 
    ~Pimpl(); 
    Pimpl(const Pimpl&); 
    Pimpl& operator(const Pimpl&); 

private: 
    Object* mObject; 
    deleter_pointer mDeleter; 
}; 

C'est un idiome classique en C++, ajouter encore un autre niveau d'indirection :)

+0

Est-ce le pointeur intelligent pour tenir l'implémentation? Ne pas booster scoped_ptr et shared_ptr supporte déjà les types incomplets? – visitor