116 votes

C ++ - Passage de références à boost :: shared_ptr

Si j'ai une fonction qui doit travailler avec un shared_ptr, ne serait-il pas plus efficace de passer une référence (afin d'éviter de copier les shared_ptr objet)? Quels sont les possibles effets secondaires néfastes? Je vois deux cas de figure possibles:

1) à l'intérieur de la fonction, une copie de l'argument, comme dans

ClassA::take_copy_of_sp(boost::shared_ptr<foo> &sp)  
{  
     ...  
     m_sp_member=sp; //This will copy the object, incrementing refcount  
     ...  
}

2) à l'intérieur de la fonction, l'argument n'est utilisé, comme dans

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}

Je ne peux pas voir dans les deux cas, une bonne raison de passer le boost::shared_ptr par valeur et non par référence. Passage par valeur serait seulement "temporairement" incrémenter le compteur de référence en raison de la copie, puis décrémenter il en sortant de la portée de la fonction. Suis-je surplombant quelque chose?

Andrea.

EDIT:
Juste pour préciser, après la lecture de plusieurs réponses : - je parfaitement d'accord sur le prématuré-optimisation des préoccupations, et j'ai toujours essayer de premier profil-puis-travail-sur-le-hotspots. Ma question était plus un aspect purement technique code-point-de-vue, si vous savez ce que je veux dire.

117voto

Jon Points 2160

Je me suis trouvé en désaccord avec la plus haute voté réponse, je suis donc à la recherche d'un expert opinons et ils sont ici. À partir de http://channel9.msdn.com/Shows/Going+Deep/C-et-au-Delà-2011-Scott-André-et-Herbe-Demandez-Nous Tout

Herb Sutter: "lorsque vous passez des shared_ptr, les copies sont chers"

Scott Meyers: "Il n'y a rien de spécial à propos de shared_ptr quand il s'agit de savoir si vous passer par valeur, ou de le passer par référence. Utiliser exactement la même analyse que vous utilisez pour tout autre type défini par l'utilisateur. Les gens semblent avoir cette perception que shared_ptr en quelque sorte résout tous les problèmes de gestion, et que parce qu'elle est petite, c'est forcément bon marché pour passer par valeur. Il doit être copié, et il y a un coût associé avec ça... c'est cher de passer par valeur, donc si je peux sortir avec elle avec une bonne sémantique dans mon programme, je vais le passer par référence const ou de référence à la place"

Herb Sutter: "toujours les passer par référence const, et très occasionnellement, peut-être parce que vous savez ce que vous avez appelé susceptible de modifier la chose que vous avez une référence, peut-être alors vous pourriez passer par valeur... si vous les copier en tant que paramètres, oh mon dieu, vous avez presque jamais besoin de bosse qui compte de référence, car il lieu vivant de toute façon, et vous devriez être le passage par référence, donc merci de le faire"

Mise à jour: Herbe s'est étendu sur ce sujet ici: http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/, bien que la morale de l'histoire est que vous ne devriez pas être en train de passer shared_ptr à tous "si vous souhaitez utiliser ou de manipuler le pointeur intelligent lui-même, comme de partager ou de transférer la propriété."

114voto

Daniel Earwicker Points 63298

Le point distinct shared_ptr de l'instance est de garantir autant que possible) que tant que ce shared_ptr est dans la portée, l'objet qu'il points de, parce que son compte de référence sera d'au moins 1.

Class::only_work_with_sp(boost::shared_ptr<foo> sp)
{
    // sp points to an object that cannot be destroyed during this function
}

Ainsi, en utilisant une référence à un shared_ptr, vous désactivez la garantie. Donc, dans votre deuxième cas:

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}

Comment savez-vous que sp->do_something() ne va pas exploser en raison d'un pointeur null?

Tout dépend de ce qui est dans les '...' articles du code. Que faire si vous appelez quelque chose au cours de la première '...' qui a des effets secondaires (quelque part dans une autre partie du code), de la compensation d'une shared_ptr de ce même objet? Et que si elle arrive à être le seul restant distinct shared_ptr de cet objet? Bye Bye objet, juste à l'endroit où vous êtes sur le point d'essayer de l'utiliser.

Il y a donc deux façons de répondre à cette question:

  1. Examiner la source de l'ensemble de votre programme très soigneusement jusqu'à ce que vous êtes sûr que l'objet ne meurent pas pendant le corps de la fonction.

  2. Modifier le paramètre de retour à un objet distincts au lieu d'une référence.

Général peu de conseil qui s'applique ici: ne vous souciez pas faire risqué de changer votre code pour des raisons de performance jusqu'à ce que vous avez chronométré votre produit dans une situation réaliste dans un profiler et mesurée de façon concluante que le changement que vous voulez faire va faire une différence significative de rendement.

Mise à jour pour l'intervenant JQ

Voici un exemple artificiel. C'est délibérément simple, de sorte que l'erreur sera évident. Dans des exemples concrets, l'erreur n'est pas si évident, parce qu'il est caché dans les couches de détail réel.

Nous avons une fonction qui va envoyer un message quelque part. Il peut être un grand message d'une façon plutôt que d'utiliser un std::string que probablement copié comme il est passé autour de plusieurs endroits, nous utilisons un shared_ptr chaîne:

void send_message(std::shared_ptr<std::string> msg)
{
    std::cout << (*msg.get()) << std::endl;
}

(Nous venons de l'envoyer à la console pour cet exemple).

Maintenant, nous voulons ajouter un établissement à retenir le message précédent. Nous voulons que le comportement suivant: une variable doit exister qui contient le plus récemment envoyé un message, mais alors qu'un message est actuellement envoyé alors il doit y avoir aucun message précédent (la variable doit être réinitialisé avant de l'envoyer). Si nous déclarons la variable:

std::shared_ptr<std::string> previous_message;

Puis nous avons modifier notre fonction selon les règles que nous avons spécifié:

void send_message(std::shared_ptr<std::string> msg)
{
    previous_message = 0;
    std::cout << *msg << std::endl;
    previous_message = msg;
}

Donc, avant de commencer l'envoi de nous jeter l'actuel message précédent, et puis après l'envoi est terminé, nous pouvons stocker le nouveau message précédent. Du tout bon. Voici un code de test:

send_message(std::shared_ptr<std::string>(new std::string("Hi")));
send_message(previous_message);

Et comme prévu, cela s'imprime Hi! deux fois.

Maintenant vient le long M. de Responsable, qui regarde le code et pense: Hé, que le paramètre send_message est shared_ptr:

void send_message(std::shared_ptr<std::string> msg)

Évidemment, cela peut être changé:

void send_message(const std::shared_ptr<std::string> &msg)

Pensez à l'amélioration de la performance de cette apportera! (Ne jamais oublier que nous sommes sur le point d'envoyer un message de grande taille sur certains canaux, de sorte que l'amélioration de la performance sera si faible qu'il peut être unmeasureable).

Mais le vrai problème est que maintenant le code de test présentent un comportement indéterminé (dans Visual C++ 2010 debug, il se bloque).

Monsieur le Responsable est surpris par cela, mais il ajoute une défensive vérifiez send_message dans une tentative pour éviter que le problème se produit:

void send_message(const std::shared_ptr<std::string> &msg)
{
    if (msg == 0)
        return;

Mais bien sûr, il va toujours de l'avant et se bloque, parce qu' msg n'est jamais null lorsque l' send_message est appelé.

Comme je l'ai dit, avec tout le code de tellement proches dans un exemple trivial, il est facile de trouver l'erreur. Mais dans la vraie programmes, avec des relations plus complexes entre les objets mutables qui détiennent des pointeurs vers les uns des autres, il est facile de faire l'erreur, et difficile à construire le nécessaire en cas de test pour détecter l'erreur.

La solution de facilité, où vous voulez une fonction de pouvoir compter sur un shared_ptr continue à être non-nulle dans l'ensemble, est pour la fonction à attribuer ses propres true shared_ptr, plutôt que de s'appuyer sur une référence à un shared_ptr.

L'inconvénient est qu'un copié shared_ptr n'est pas libre: même "lock-free" implémentations utiliser un contrefil opération à l'honneur le filetage de garanties. Il peut donc y avoir des situations où un programme peut être considérablement accéléré par la modification d'un shared_ptr en shared_ptr &. Mais ce n'est pas un changement qui peut être fait en toute sécurité à tous les programmes. Il modifie le sens logique du programme.

Notez qu'un bug similaire pourrait se produire si nous avons utilisé std::string tout au long de la place de std::shared_ptr<std::string>, et, au lieu de:

previous_message = 0;

pour effacer le message, nous dit:

previous_message.clear();

Alors le symptôme serait la date de l'envoi d'un message vide, au lieu d'un comportement indéterminé. Le coût d'une copie supplémentaire d'une très grande chaîne peut être beaucoup plus important que le coût de la copie d'un shared_ptr, de sorte que le compromis peut être différent.

23voto

littlenag Points 2124

Je conseille à l'encontre de cette pratique, à moins que vous et les autres programmeurs qui travaillent avec vous vraiment, vraiment savoir ce que vous faites tous.

Tout d'abord, vous n'avez aucune idée de la façon dont l'interface à votre classe peut évoluer et vous souhaitez empêcher les autres programmeurs de faire de mauvaises choses. Le passage d'un shared_ptr par référence n'est pas quelque chose d'un programmeur doit s'attendre à le voir, car il n'est pas idiomatique, et qui le rend facile à utiliser de manière incorrecte. Programme, sur la défensive: rendre l'interface difficile à utiliser correctement. Le passage par référence est simplement d'aller inviter des problèmes plus tard.

Deuxièmement, ne pas optimiser jusqu'à ce que vous savez cette classe particulière va être un problème. Profil d'abord, et ensuite, si votre programme a vraiment besoin d'un coup de pouce donné par le passage par référence, alors peut-être. Sinon, ne pas transpirer les petites choses (c'est à dire le supplément de N instructions de passer par valeur) au lieu inquiéter de la conception, de structures de données, algorithmes, et la maintenance à long terme.

18voto

Oui, la prise de référence est bien là. Vous n'avez pas l'intention de donner la méthode de partage de la propriété; il ne veut travailler avec elle. Vous pourriez prendre une référence pour le premier cas, puisque vous copie de toute façon. Mais pour le premier cas, il prend possession. Il y a ce truc de toujours copier qu'une seule fois:

void ClassA::take_copy_of_sp(boost::shared_ptr<foo> sp) {
    m_sp_member.swap(sp);
}

Vous devez également copier lorsque vous le retourner (j'.e de ne pas renvoyer une référence). Parce que votre classe ne sais pas ce que le client est en train de faire avec elle (il peut stocker un pointeur et puis big bang qui se passe). Si plus tard, il s'avère que c'est un goulot d'étranglement (premier profil!), ensuite, vous pouvez toujours retourner une référence.


Edit: bien sûr, comme d'autres font remarquer, ce n'est vrai que si vous savez que votre code et que vous ne voulez pas réinitialiser le passé pointeur partagé, d'une certaine façon. En cas de doute, il suffit de passer par valeur.

11voto

Magnus Hoff Points 12052

Il est judicieux de passer shared_ptrs par const&. Il ne sera pas susceptible de causer des problèmes (sauf dans le cas peu probable que les renvois shared_ptr est supprimé lors de l'appel de la fonction, comme détaillé par Earwicker) et il sera probablement plus rapide si vous passez beaucoup de ces autour. Rappelez-vous; la valeur par défaut boost::shared_ptr est thread-safe, donc la copie il comprend un thread-safe de l'incrément.

Essayez d'utiliser const& plutôt que de simplement en &, parce que les objets temporaires ne peuvent pas être transmises par des non-const de référence. (Même si une extension du langage dans MSVC vous permet de le faire de toute façon)

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