Quelles sont les façons de se tirer une balle dans le pied lorsqu'on utilise boost::shared_ptr
? En d'autres termes, quels sont les écueils que je dois éviter lorsque j'utilise boost::shared_ptr
?
Réponses
Trop de publicités?Références cycliques : a shared_ptr<>
à quelque chose qui a un shared_ptr<>
à l'objet original. Vous pouvez utiliser weak_ptr<>
pour briser ce cycle, bien sûr.
J'ajoute ce qui suit comme exemple de ce dont je parle dans les commentaires.
class node : public enable_shared_from_this<node> {
public :
void set_parent(shared_ptr<node> parent) { parent_ = parent; }
void add_child(shared_ptr<node> child) {
children_.push_back(child);
child->set_parent(shared_from_this());
}
void frob() {
do_frob();
if (parent_) parent_->frob();
}
private :
void do_frob();
shared_ptr<node> parent_;
vector< shared_ptr<node> > children_;
};
Dans cet exemple, vous avez un arbre de nœuds, chacun d'entre eux contenant un pointeur vers son parent. La fonction membre frob(), pour une raison ou une autre, se répercute vers le haut de l'arbre (ce n'est pas totalement excentrique ; certains cadres d'interface graphique fonctionnent de cette façon).
Le problème est que, si vous perdez la référence au nœud le plus haut, ce dernier conserve des références fortes à ses enfants, et tous ses enfants conservent également une référence forte à leurs parents. Cela signifie qu'il existe des références circulaires empêchant toutes les instances de se nettoyer, alors qu'il n'y a aucun moyen d'atteindre réellement l'arbre à partir du code, cette fuite de mémoire.
class node : public enable_shared_from_this<node> {
public :
void set_parent(shared_ptr<node> parent) { parent_ = parent; }
void add_child(shared_ptr<node> child) {
children_.push_back(child);
child->set_parent(shared_from_this());
}
void frob() {
do_frob();
shared_ptr<node> parent = parent_.lock(); // Note: parent_.lock()
if (parent) parent->frob();
}
private :
void do_frob();
weak_ptr<node> parent_; // Note: now a weak_ptr<>
vector< shared_ptr<node> > children_;
};
Ici, le nœud parent a été remplacé par un pointeur faible. Il n'a plus son mot à dire sur la durée de vie du nœud auquel il fait référence. Ainsi, si le noeud le plus haut sort de la portée comme dans l'exemple précédent, alors qu'il détient des références fortes à ses enfants, ses enfants ne détiennent pas de références fortes à leurs parents. Il n'y a donc pas de références fortes à l'objet, et celui-ci se nettoie tout seul. À leur tour, les enfants perdent leur seule référence forte, ce qui les oblige à se nettoyer, et ainsi de suite. En bref, il n'y a pas de fuite. Et juste en remplaçant stratégiquement un shared_ptr<> avec un weak_ptr<>.
Note : Ce qui précède s'applique aussi bien à std::shared_ptr<> et std::weak_ptr<> qu'à boost::shared_ptr<> et boost::weak_ptr<>.
Création de plusieurs éléments non liés shared_ptr
pour le même objet :
#include <stdio.h>
#include "boost/shared_ptr.hpp"
class foo
{
public:
foo() { printf( "foo()\n"); }
~foo() { printf( "~foo()\n"); }
};
typedef boost::shared_ptr<foo> pFoo_t;
void doSomething( pFoo_t p)
{
printf( "doing something...\n");
}
void doSomethingElse( pFoo_t p)
{
printf( "doing something else...\n");
}
int main() {
foo* pFoo = new foo;
doSomething( pFoo_t( pFoo));
doSomethingElse( pFoo_t( pFoo));
return 0;
}
Construire un pointeur partagé temporaire anonyme, par exemple dans les arguments d'un appel de fonction :
f(shared_ptr<Foo>(new Foo()), g());
C'est parce qu'il est permis pour le new Foo()
pour être exécuté, alors g()
appelé, et g()
pour lancer une exception, sans le shared_ptr
jamais été mis en place, de sorte que le shared_ptr
n'a pas la possibilité de nettoyer le Foo
objeto.
Attention, il faut faire deux pointeurs sur le même objet.
boost::shared_ptr<Base> b( new Derived() );
{
boost::shared_ptr<Derived> d( b.get() );
} // d goes out of scope here, deletes pointer
b->doSomething(); // crashes
utilisez plutôt ceci
boost::shared_ptr<Base> b( new Derived() );
{
boost::shared_ptr<Derived> d =
boost::dynamic_pointer_cast<Derived,Base>( b );
} // d goes out of scope here, refcount--
b->doSomething(); // no crash
De plus, toutes les classes contenant des shared_ptrs doivent définir des constructeurs de copie et des opérateurs d'affectation.
N'essayez pas d'utiliser shared_from_this() dans le constructeur - cela ne fonctionnera pas. Créez plutôt une méthode statique pour créer la classe et faites en sorte qu'elle renvoie un shared_ptr.
J'ai passé des références à des shared_ptrs sans problème. Assurez-vous simplement qu'elles sont copiées avant d'être sauvegardées (c'est-à-dire, pas de références en tant que membres de classe).
Voici deux choses à éviter :
-
Appeler le
get()
pour obtenir le pointeur brut et l'utiliser après que l'objet pointé soit sorti de sa portée. -
Transmettre une référence ou un pointeur brut à un fichier
shared_ptr
devrait être dangereux aussi, puisqu'il n'incrémentera pas le compte interne qui aide à maintenir l'objet en vie.
- Réponses précédentes
- Plus de réponses