49 votes

Quels sont les dangers potentiels de l'utilisation de boost::shared_ptr ?

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 ?

42voto

Kaz Dragon Points 3460

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<>.

25voto

Michael Burr Points 181287

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;
}

18voto

Brian Campbell Points 101107

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.

13voto

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).

12voto

Frank Points 16055

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.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X