Question

Lorsque vous utilisez le idiome pImpl , il est préférable d'utiliser un boost:shared_ptr au lieu d'un std::auto_ptr? Je suis sûr d'avoir lu une fois que la version boost était plus favorable aux exceptions?

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

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

[EDIT] Est-il toujours prudent d'utiliser std :: auto_ptr < > ou existe-t-il des situations dans lesquelles un pointeur intelligent alternatif est requis?

Était-ce utile?

La solution

Vous ne devriez pas vraiment utiliser std :: auto_ptr pour cela. Le destructeur ne sera pas visible au moment où vous déclarez le std :: auto_ptr, il pourrait donc ne pas être appelé correctement. Cela suppose que vous déclarez en avant votre classe pImpl et que vous créez l'instance à l'intérieur du constructeur dans un autre fichier.

Si vous utilisez boost :: scoped_ptr ( pas besoin de shared_ptr ici, vous ne partagerez pas le pimpl avec d'autres objets, ce qui est imposé par scoped_ptr étant noncopyable ), vous n’avez besoin que du destructeur pimpl visible au point où vous appelez le constructeur scoped_ptr.

ex.

// in MyClass.h

class Pimpl;

class MyClass 
{ 
private:
    std::auto_ptr<Pimpl> pimpl;

public: 
    MyClass();
};

// Body of these functions in MyClass.cpp

Ici, le compilateur générera le destructeur de MyClass. Qui doit appeler le destructeur de auto_ptr. Au moment où le destructeur auto_ptr est instancié, Pimpl est un type incomplet. Donc, dans le destructeur auto_ptr quand il supprime l’objet Pimpl, il ne saura pas appeler le destructeur Pimpl.

boost :: scoped_ptr (et shared_ptr) n'ont pas ce problème, car lorsque vous appelez le constructeur d'un scoped_ptr (ou la méthode reset), il crée également un équivalent de fonction de pointeur qu'il utilisera au lieu d'appeler delete . Le point clé ici est qu’il instancie la fonction de désallocation lorsque Pimpl n’est pas un type incomplet. De plus, shared_ptr vous permet de spécifier un désallocation personnalisée fonction, vous pouvez donc l'utiliser pour des tâches telles que les handles GDI ou tout autre chose que vous voudriez, mais c'est exagéré pour vos besoins ici.

Si vous voulez vraiment utiliser std :: auto_ptr, vous devez être particulièrement prudent en vous assurant de définir votre destructeur MyClass dans MyClass.cpp lorsque Pimpl est complètement défini.

// in MyClass.h

class Pimpl;

class MyClass 
{ 
private:
    std::auto_ptr<Pimpl> pimpl;

public: 
    MyClass();
    ~MyClass();
};

et

// in MyClass.cpp

#include "Pimpl.h"

MyClass::MyClass() : pimpl(new Pimpl(blah))
{
}

MyClass::~MyClass() 
{
    // this needs to be here, even when empty
}

Le compilateur générera le code détruisant tous les membres de MyClass de manière efficace "dans" le destructeur vide. Donc, au moment où le destructeur auto_ptr est instancié, Pimpl n’est plus incomplet et le compilateur sait maintenant comment appeler le destructeur.

Personnellement, je ne pense pas que cela vaille la peine de s’assurer que tout est correct. Il y a aussi le risque que quelqu'un vienne plus tard et nettoie le code en supprimant le destructeur apparemment redondant. Donc, il est simplement plus sûr d'utiliser boost :: scoped_ptr pour ce genre de choses.

Autres conseils

J'ai tendance à utiliser auto_ptr. Assurez-vous de rendre votre classe non copiable (déclarez la copie privée ctor & Et opérateur =, sinon héritez de boost::noncopyable). Si vous utilisez impl, vous devez définir un destructeur non intégré, même si le corps est vide. (Cela est dû au fait que si vous laissez le compilateur générer le destructeur par défaut, delete impl_ sera un type incomplet lorsque l'appel à <=> est généré, invoquant un comportement non défini).

Il y a peu de choix entre <=> & amp; les pointeurs de boost. J'ai tendance à ne pas utiliser boost pour des raisons stylistiques si une bibliothèque standard alternative suffira.

boost :: shared_ptr est spécialement conçu pour fonctionner avec l'idiome pimpl. L'un des principaux avantages est qu'il permet de ne pas définir le destructeur de la classe détenant pimpl. Une politique de propriété partagée peut être à la fois un avantage et un inconvénient. Mais dans la suite des cas, vous pourrez définir correctement le constructeur de copie.

Si vous êtes vraiment pédant, il n’existe aucune garantie absolue que l’utilisation d’un membre auto_ptr ne nécessite pas une définition complète du paramètre de modèle de const auto_ptr au point où il est utilisé. Cela dit, je n'ai jamais vu cela ne pas fonctionner.

Une variante consiste à utiliser un <=>. Cela fonctionne tant que vous pouvez construire votre 'pimpl' avec une nouvelle expression dans la liste des initialiseurs et garantir que le compilateur ne peut pas générer le constructeur de copie et les méthodes d'attribution par défaut. Un destructeur non intégré à la classe englobante doit encore être fourni.

Toutes choses étant égales par ailleurs, je serais favorable à une implémentation qui utilise uniquement les bibliothèques standard car elle permet de rendre les choses plus portables.

Si vous voulez une classe copiable, utilisez scoped_ptr, qui interdit la copie, rendant ainsi votre classe difficile à utiliser par défaut (par rapport à shared_ptr, le compilateur n'émettra pas de fonctions de copie par lui-même; et dans le cas de <=>, si vous ne savez pas ce que vous faites [ce qui est assez souvent le cas même pour les sorciers], il y aura un comportement étrange quand soudainement une copie de quelque chose le modifiera également), puis sur-définit un constructeur de copie et une assignation de copie:

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 est de loin préférable à auto_ptr pour pImpl car votre classe externe risque de perdre son pointeur lorsque vous la copiez.

Avec shared_ptr, vous pouvez utiliser un type déclaré en aval pour que cela fonctionne. auto_ptr n'autorise pas un type déclaré en avant. Scoped_ptr ne le fait pas non plus, et si votre classe externe doit être non copiable de toute façon et ne comporter qu'un seul pointeur, il peut également s'agir d'un pointeur normal.

Il y a beaucoup à dire sur l'utilisation d'un compte de références intrusives dans pImpl et oblige la classe externe à appeler sa copie et à attribuer une sémantique à sa mise en œuvre. En supposant qu'il s'agisse d'un modèle de fournisseur réel (fournit la classe), il est préférable que le fournisseur n'oblige pas l'utilisateur à utiliser shared_ptr, ni à utiliser la même version de shared_ptr (boost ou std).

J'ai été vraiment heureux de impl_ptr de Vladimir Batov [modifié] . Il est très facile de créer un pImpl sans avoir à créer un constructeur de copie et un opérateur d’attribution explicites.

J'ai modifié le code d'origine afin qu'il ressemble maintenant à un shared_ptr. Il peut donc être utilisé dans le code Epilog et reste rapide.

N'essayez pas si fort de vous tirer une balle dans le pied, en C ++, vous avez beaucoup d'opportunités :) Il n’est pas vraiment nécessaire d’utiliser les pointeurs automatiques, car vous savez parfaitement quand votre objet doit entrer et sortir de la vie (dans vos constructeurs et destructeurs).

Restez simple.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top