Question

Quels sont les moyens que vous pouvez vous tirer dans le pied lors de l'utilisation boost::shared_ptr ? Autrement dit, quels sont les écueils que je dois éviter quand je l'utilise boost::shared_ptr ?

Était-ce utile?

La solution

références cycliques: un shared_ptr<> à quelque chose qui a un shared_ptr<> à l'objet d'origine. Vous pouvez utiliser weak_ptr<> pour briser ce cycle, bien sûr.


ajouter ce qui suit comme un 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, dont chacun détient un pointeur vers son parent. La fonction membre FRAB (), pour une raison quelconque, ondulations vers le haut à travers l'arbre. (Ce n'est pas tout à fait farfelu, certains cadres GUI fonctionnent de cette manière).

Le problème est que, si vous perdez référence au nœud le plus élevé, le noeud le plus élevé tient toujours de fortes références à ses enfants, et tous ses enfants tiennent aussi une référence forte à leurs parents. Cela signifie qu'il ya des références circulaires en gardant toutes les instances de se nettoyer, alors qu'il n'y a aucun moyen d'atteindre effectivement 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 dans la durée de vie du nœud auquel il se réfère. Ainsi, si le nœud le plus haut est hors de portée comme dans l'exemple précédent, puis alors qu'il détient de fortes références à ses enfants, ses enfants ne tiennent pas de fortes références à leurs parents. Ainsi, il n'y a pas de fortes références à l'objet, et il se nettoie. À son tour, ce qui provoque les enfants à perdre leur une référence forte, ce qui les amène à nettoyer, et ainsi de suite. En bref, cette fuite habitude. Et en remplaçant stratégiquement un shared_ptr <> avec un weak_ptr <>.

Note:. Le applique ci-dessus également à std :: shared_ptr <> et std :: weak_ptr <> comme il le fait pour stimuler :: shared_ptr <> et boost :: weak_ptr <>

Autres conseils

La création de plusieurs shared_ptr non apparentés de la 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 à l'intérieur des arguments à un appel de fonction:

f(shared_ptr<Foo>(new Foo()), g());

En effet, il est permis au new Foo() à exécuter, puis g() appelé et g() à jeter une exception, sans shared_ptr jamais être mis en place, de sorte que le shared_ptr n'a pas de chance de nettoyer l'objet Foo.

Veillez à 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 cette

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

En outre, toutes les classes de maintien shared_ptrs devraient définir les constructeurs de copie et les opérateurs d'affectation.

Ne pas essayer d'utiliser shared_from_this () dans le constructeur - il ne fonctionnera pas. Au lieu de créer une méthode statique pour créer la classe et l'ont un retour shared_ptr.

J'ai passé des références à shared_ptrs sans peine. Assurez-vous qu'il est copié avant qu'il ne soit sauvé (à savoir, aucune référence en tant que membres de la classe).

Voici deux choses à éviter:

  • L'appel de la fonction get() pour obtenir le pointeur brut et de l'utiliser après la pointe à objet est hors de portée.

  • Le passage d'une référence ou un pointeur brut à un shared_ptr devrait être trop dangereux, car il ne sera pas incrémenter le compteur interne qui permet de garder l'objet vivant.

Nous déboguer plusieurs semaines un comportement étrange.

La raison était: nous avons passé « ceci » à certains travailleurs de fil au lieu de « shared_from_this ».

Pas précisément un footgun, mais certainement une source de frustration jusqu'à ce que vous envelopper votre tête autour de la façon de faire le C ++ 0x façon: la plupart des prédicats que vous connaissez et l'amour de <functional> ne jouent pas bien avec shared_ptr. Heureusement, std::tr1::mem_fn travaille avec des objets, des pointeurs et shared_ptrs, remplaçant std::mem_fun, mais si vous voulez utiliser std::negate, std::not1, std::plus ou l'un de ces vieux amis avec shared_ptr, soyez prêt à obtenir confortable avec des espaces réservés std::tr1::bind et probablement arguments aussi bien. En pratique, cela est en fait beaucoup plus générique, puisque maintenant vous finissez essentiellement à l'aide bind pour chaque adaptateur d'objet de fonction, mais il prend un certain temps pour s'y habituer si vous êtes déjà familier avec les fonctions pratiques de la STL.

Cet article DDJ touche sur le sujet, avec beaucoup de code par exemple. J'ai aussi blogué à ce sujet il y a quelques années quand je devais savoir comment faire .

Utilisation shared_ptr pour des objets très petits (comme char short) pourrait être une surcharge si vous avez beaucoup de petits objets sur tas, mais ils ne sont pas vraiment « Partagé ». boost::shared_ptr alloue 16 octets pour chaque nouvelle référence crée sur le compter g ++ 4.4.3 et VS2008 avec Boost 1,42. std::tr1::shared_ptr alloue 20 octets. Maintenant, si vous avez un million shared_ptr<char> distinct qui signifie 20 millions d'octets de mémoire sont partis en tenant compte juste = 1. Sans parler des coûts d'indirection et la fragmentation de la mémoire. Essayez le suivant sur votre plateforme favorite.

void * operator new (size_t size) {
  std::cout << "size = " << size << std::endl;
  void *ptr = malloc(size);
  if(!ptr) throw std::bad_alloc();
  return ptr;
}
void operator delete (void *p) {
  free(p);
}

Donner un shared_ptr à cet intérieur d'une définition de classe est aussi dangereuse. Utilisez enabled_shared_from_this à la place.

Voir le message suivant

Vous devez être prudent lorsque vous utilisez shared_ptr dans le code multithread. Il est alors relativement facile de devenir dans un cas où deux shared_ptrs, pointant vers la même mémoire, est utilisé par différents threads.

L'utilisation généralisée populaire de shared_ptr entraînera presque inévitablement l'occupation de la mémoire non désirée et invisible.

références cycliques sont une cause bien connue et certains d'entre eux peuvent être indirects et difficiles à repérer en particulier dans le code complexe qui est travaillé par plus d'un programmeur; un programmeur peut décider d'un objet a besoin d'une référence à un autre comme une solution rapide et n'a pas le temps d'examiner tout le code pour voir s'il ferme un cycle. Ce risque est extrêmement sous-estimé.

Moins bien compris est le problème des références inédites. Si un objet est partagé à de nombreux shared_ptrs alors il ne sera pas détruite jusqu'à ce que chacun d'eux est mis à zéro ou est hors de portée. Il est très facile d'oublier une de ces références et finissent avec des objets invisibles qui rôdent dans la mémoire que vous avez pensé que vous aviez fini avec.

Bien que à proprement parler ne sont pas ces fuites de mémoire (cela va être libéré avant la fin du programme), ils sont tout aussi nocifs et plus difficiles à détecter.

Ces problèmes sont les conséquences des fausses déclarations opportunes: 1. Déclarer ce que vous voulez vraiment être propriétaire unique comme shared_ptr. scoped_ptr serait correcte mais toute autre référence à cet objet devra être un pointeur brut, ce qui pourrait être laissé ballants. 2. Déclarer ce que vous voulez vraiment être une référence observation passive shared_ptr. weak_ptr serait correct, mais vous les tracas de la convertir en share_ptr chaque fois que vous voulez utiliser.

Je pense que votre projet est un bel exemple du genre de problème que cette pratique peut vous prendre.

Si vous avez une application intensive de mémoire que vous avez vraiment besoin d'un seul propriétaire afin que votre conception peut contrôler explicitement la durée de vie de l'objet.

Avec un seul propriétaire opObject = NULL; va supprimer définitivement l'objet et il le fera maintenant.

Avec la propriété partagée spObject = NULL; ........ qui sait? ......

Si vous avez un registre des objets partagés (une liste de toutes les instances actives, par exemple), les objets ne seront jamais libérés. Solution:. Comme dans le cas des structures de dépendance circulaire (voir la réponse de Kaz dragon), utilisez weak_ptr selon le cas

pointeurs intelligents ne sont pas pour tout, et les pointeurs premières ne peuvent pas être éliminés

Probablement le pire danger est que depuis shared_ptr est un outil utile, les gens vont commencer à le mettre partout. Étant donné que les pointeurs simples peuvent être mal utilisés, les mêmes gens chasser des pointeurs premières et essayer de les remplacer par des chaînes, des conteneurs ou des pointeurs intelligents, même quand il n'a pas de sens. Les utilisations légitimes des pointeurs premières deviendront suspects. Il y aura une police pointeur.

Ceci est non seulement probablement le pire danger, il peut être le seul danger. Tous les pires abus de shared_ptr seront la conséquence directe de l'idée que les pointeurs intelligents sont supérieurs à pointeur brut (quoi que cela signifie), et que la mise des pointeurs intelligents partout fera la programmation C ++ « plus sûr ».

Bien sûr, le simple fait qu'un pointeur intelligent doit être converti en un pointeur première à utiliser réfute cette affirmation du culte de pointeur intelligent, mais le fait que l'accès au pointeur brut est « implicite » dans operator*, operator-> (ou explicite get()), mais pas implicite dans une conversion implicite, est suffisant pour donner l'impression que ce n'est pas vraiment une conversion, et que le pointeur brut produit par ce non-conversion est une inoffensive temporaire.

C ++ ne peut pas être fait un « langage sûr », et aucun sous-ensemble utile de C ++ est « sûr »

Bien sûr, la poursuite d'un sous-ensemble de sécurité ( « sûr » au sens strict de la « mémoire sûre », comme LISP, Haskell, Java ...) de C ++ est vouée à être sans fin et peu satisfaisant, comme le sous-ensemble en toute sécurité C ++ est minuscule et presque inutile, comme primitives dangereuses sont la règle plutôt que l'exception. sécurité mémoire stricte en C ++ signifierait pas de pointeurs et uniquement des références avec la classe de stockage automatique . Mais dans une langue où le programmeur est digne de confiance par définition , certaines personnes insistent sur l'utilisation de certains (en principe) l'épreuve idiot « pointeur intelligent », même là où il n'y a pas d'autre avantage sur les pointeurs premières < em> d'une manière spécifique à vis est évité l'état du programme.

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