C++ - passer des références à std:: partagé ptr ou boost:: partagé ptr

si j'ai une fonction qui doit fonctionner avec un shared_ptr , ne serait-il pas plus efficace de lui transmettre une référence (pour éviter de copier l'objet shared_ptr )? Quels sont les effets secondaires indésirables possibles? J'envisage deux cas 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) dans la fonction l'argument est seulement 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<foo> par valeur et non par référence. Le fait de passer par la valeur ne ferait qu'augmenter "temporairement" le nombre de référence dû à la copie, puis le décrémenterait en sortant de la portée de la fonction. Suis-je surplombant quelque chose?

juste pour clarifier, après avoir lu plusieurs réponses: je suis parfaitement d'accord sur les préoccupations d'optimisation prématurée, et j'essaie toujours d'abord-profiler-puis-travailler-sur-les-points chauds. Ma question était plus de purement point de vue du code technique, si vous voyez ce que je veux dire.

112
demandé sur Null 2008-11-29 17:27:31

17 réponses

le point d'une instance distincte shared_ptr est de garantir (dans la mesure du possible) que tant que cette shared_ptr aura une portée, l'objet qu'elle désigne existera toujours, parce que son compte de référence sera au moins égal à 1.

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

donc en utilisant une référence à un shared_ptr , vous désactivez cette garantie. Ainsi, 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() n'explosera pas à cause d'un nul pointeur?

tout dépend de ce qu'il y a là-dedans... sections du code. Et si tu appelais quelque chose pendant la première fois?..'qui a l'effet secondaire (quelque part dans une autre partie du code) d'effacer un shared_ptr de ce même objet? Et s'il s'avère que c'est le seul shared_ptr qui reste distinct de cet objet? Bye Bye objet, juste là où vous êtes sur le point d'essayer de l'utiliser.

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

  1. " examinez très attentivement la source de votre programme entier jusqu'à ce que vous soyez sûr que l'objet ne mourra pas pendant le corps de fonction.

  2. changez le paramètre de nouveau pour être un objet distinct au lieu d'une référence.

petit conseil général qui s'applique ici: ne prenez pas la peine d'apporter des changements risqués à votre code pour le plaisir de la performance jusqu'à ce que vous ayez chronométré votre produit dans une situation réaliste dans un profileur et mesuré de façon concluante que le changement que vous voulez faire fera une différence significative à la performance.

mise à jour pour le commentateur JQ

Voici un exemple artificiel. C'est délibérément simple, donc l'erreur sera évidente. Dans les exemples réels, l'erreur n'est pas si évidente parce qu'elle est cachée dans des couches de détails réels.

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

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

(nous l'envoyons simplement à la console pour cet exemple).

maintenant nous voulons ajouter une facilité pour se souvenir du message précédent. Nous voulons le comportement suivant: une variable doit exister qui contient le message envoyé le plus récemment, mais pendant qu'un message est actuellement envoyé, il ne doit pas y avoir de message précédent (la variable doit être réinitialisée avant l'envoi). Nous déclarons donc la nouvelle variable:

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

puis nous modifions notre fonction selon les règles que nous avons spécifiées:

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

donc, avant que nous commencions à envoyer, nous supprimons le message précédent actuel, et après l'envoi est terminé, nous pouvons stocker le nouveau message précédent. Du tout bon. Voici du code de test.:

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

et comme prévu, cela imprime Hi! deux fois.

arrive maintenant Mr mainteneur, qui regarde le code et pense: Hé, ce paramètre à send_message est un shared_ptr :

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

évidemment que peut être changé en:

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

pensez à l'amélioration de la performance que cela va apporter! (Peu importe que nous soyons sur le point d'envoyer un message typiquement canal, de sorte que l'amélioration de la performance sera si petit que d'être impossible à mesurer).

mais le vrai problème est que maintenant le code de test présentera un comportement non défini (dans les constructions de débogage de Visual C++ 2010, il s'écrase).

M. mainteneur est surpris par cela, mais ajoute un contrôle défensif à send_message dans une tentative d'arrêter le problème se produire:

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 brise, parce que msg n'est jamais null quand send_message est appelé.

comme je le dis, avec tout le code SI PROCHE dans un exemple trivial, il est facile de trouver l'erreur. Mais dans les programmes réels, avec des relations plus complexes entre des objets mutables qui tiennent des pointeurs les uns aux autres, il est facile de faire l'erreur, et difficile de construire les cas de test nécessaires pour détecter l'erreur.

la solution facile, où vous voulez un fonction pour être en mesure de compter sur un shared_ptr continuer à être non-null tout au long, est pour la fonction d'attribuer son propre vrai shared_ptr , plutôt que de compter sur une référence à un shared_ptr existant .

l'inconvénient est que copié un shared_ptr n'est pas libre: même les implémentations" sans verrouillage " doivent utiliser une opération entrelacée pour honorer les garanties de filetage. Ainsi, il peut y avoir des situations où un programme peut être accéléré de façon importante en changeant 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 bogue similaire se produirait si nous utilisions std::string tout au long au lieu de std::shared_ptr<std::string> , et au lieu de:

previous_message = 0;

pour clarifier le message, nous avons dit:

previous_message.clear();

alors le symptôme serait l'envoi accidentel d'un message vide, au lieu de comportement indéfini. 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.

105
répondu Daniel Earwicker 2011-12-02 12:44:44

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. De http://channel9.msdn.com/Shows/Going+Deep/C-et-au-Delà-2011-Scott-André-et-Herbe-Demandez-Nous-Rien

Herb Sutter: "quand vous passez shared_ptr's, les copies sont chères "

Scott Meyers: "il n'y a rien de spécial dans shared_ptr quand il s'agit de savoir si vous le passez par la valeur, ou 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 résout en quelque sorte tous les problèmes de gestion, et que parce qu'il est petit, il est nécessairement peu coûteux de passer par la valeur. Il doit être copié, et il y a un coût associé... il est coûteux de passer par la valeur, donc si je peux m'en sortir avec la bonne sémantique dans mon programme, je vais passer par référence à const ou référence à la place"

Herb Sutter: "toujours passer par référence à const, et très occasionnellement peut-être parce que vous savez ce que vous appelez pourrait modifier la chose que vous avez obtenu une référence de, peut-être alors vous pourriez passer par la valeur... si vous les copiez comme paramètres, Oh mon Dieu, vous n'avez presque jamais besoin de Bumper ce compte de référence parce qu'il est tenu en vie de toute façon, et vous devriez le passer par référence, alors s'il vous plaît faites que "

mise à jour: Herb a développé sur ce 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 passer shared_ptr's du tout " sauf si vous voulez utiliser ou manipuler le pointeur intelligent lui-même, comme pour partager ou transférer la propriété."

108
répondu Jon 2013-06-06 00:24:23

je déconseille cette pratique à moins que vous et les autres programmeurs avec qui vous travaillez vraiment, vraiment savez ce que vous faites tous.

tout d'abord, vous n'avez aucune idée de la façon dont l'interface de votre classe pourrait évoluer et vous voulez empêcher les autres programmeurs de faire de mauvaises choses. Passer un shared_ptr par référence n'est pas quelque chose qu'un programmeur devrait s'attendre à voir, parce qu'il n'est pas idiomatique, et cela le rend facile à utiliser incorrectement. Programme défensivement: rendre l'interface difficile à utiliser incorrectement. Le fait de passer par référence ne fera que poser 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 de l'impulsion donnée en passant par référence, alors peut-être. Sinon, ne vous inquiétez pas des petites choses (c.-à-d. les instructions supplémentaires N qu'il faut passer par la valeur) au lieu de se soucier de la conception, les structures de données, algorithmes et maintenabilité à long terme.

22
répondu Mark Kegel 2008-11-29 16:32:32

Oui, prendre une référence est très bien ici. Vous n'avez pas l'intention de donner la propriété partagée de la méthode; il veut seulement 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 propriété. Il y a cette astuce pour ne la 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 retournez (I. e not return a reference). Parce que votre classe ne sait pas ce que fait le client (il peut stocker un pointeur et puis big bang qui se passe). S'il s'avère plus tard que c'est un goulot d'étranglement (premier profil!), alors vous pouvez toujours retourner une référence.


modifier : bien sûr, comme d'autres le font remarquer, ceci n'est vrai que si vous connaissez votre code et savez que vous ne réinitialisez pas le pointeur partagé passé d'une manière ou d'une autre. En cas de doute, il suffit de passer par valeur.

18
répondu Johannes Schaub - litb 2008-11-30 13:49:24

il est raisonnable de passer shared_ptr s par const& . Il ne sera pas susceptible de causer des problèmes (sauf dans le cas peu probable que le référencé shared_ptr est supprimé pendant l'appel de fonction, comme détaillé par le Picoteur D'oreille) 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 comprend un incrément thread safe.

essayez d'utiliser const& plutôt que juste & , parce que temporaire les objets ne peuvent pas être passés par une référence non-const. (Même si une extension de langue dans MSVC vous permet de le faire de toute façon)

11
répondu Magnus Hoff 2008-11-30 22:30:40

dans le second cas, faire ceci est plus simple:

Class::only_work_with_sp(foo &sp)
{    
    ...  
    sp.do_something();  
    ...  
}

Vous pouvez l'appeler comme

only_work_with_sp(*sp);
10
répondu Greg Rogers 2008-11-29 16:21:54

j'éviterais une référence" simple " à moins que la fonction ne modifie explicitement le pointeur.

Un const & peut être un bon micro-optimisation lors de l'appel de petites fonctions - par exemple, pour permettre à d'autres optimisations, comme l'in-lining loin certaines conditions. Aussi, l'incrément/décrément - puisque c'est thread-safe - est un point de synchronisation. Je ne m'attends pas à ce que cela fasse une grande différence dans la plupart des scénarios.

généralement, vous devrait utiliser le style plus simple sauf si vous avez raison de ne pas le faire. Ensuite, utilisez le const & constamment, ou ajouter un commentaire pour expliquer pourquoi si vous l'utilisez juste à quelques endroits.

3
répondu peterchen 2008-11-29 19:12:27

Je préconiserais passer pointeur partagé par const Référence - une sémantique que la fonction passant avec le pointeur ne possède pas le pointeur, qui est un idiome propre pour les développeurs.

le seul piège est dans les programmes de thread multiples l'objet étant pointé par le pointeur partagé est détruit dans un autre thread. Ainsi, il est sûr de dire en utilisant la référence de const de pointeur partagé est sûr dans le programme fileté simple.

en Passant partagé pointer par référence non-const est parfois dangereux - la raison en est les fonctions de swap et de reset que la fonction peut invoquer à l'intérieur pour détruire l'objet qui est encore considéré comme valide après le retour de la fonction.

il ne s'agit pas d'optimisation prématurée, je suppose - il s'agit d'éviter le gaspillage inutile des cycles CPU quand vous êtes clair ce que vous voulez faire et l'idiome de codage a été fermement adopté par vos collègues développeurs.

Juste mes 2 cents :-)

3
répondu zmqiu 2010-03-28 19:37:28

il semble que tous les pour et les contre ici peuvent être généralisés à N'importe quel type passé par référence et pas seulement shared_ptr. À mon avis, vous devriez connaître la sémantique de passer par référence, const référence et valeur et l'utiliser correctement. Mais il n'y a absolument rien de mal à passer shared_ptr par référence, sauf si vous pensez que toutes les références sont mauvaises...

Pour revenir à l'exemple:

Class::only_work_with_sp( foo &sp ) //Again, no copy here  
{    
    ...  
    sp.do_something();  
    ...  
}

Comment allez-vous savez-vous que sp.do_something() n'explosera pas à cause d'une aiguille pendante?

la vérité est que, shared_ptr ou non, const ou non, cela pourrait se produire si vous avez un défaut de conception, Comme Partager directement ou indirectement la propriété de sp entre les fils, manquer un objet qui font delete this , vous avez une propriété circulaire ou d'autres erreurs de propriété.

3
répondu Sandy 2010-12-07 22:55:04

une chose que je n'ai pas encore vue mentionnée est que lorsque vous passez des pointeurs partagés par référence, vous perdez la conversion implicite que vous obtenez si vous voulez passer un pointeur partagé dérivé de classe à travers une référence à un pointeur partagé de classe de base.

par exemple, ce code produira une erreur, mais il fonctionnera si vous changez test() de sorte que le pointeur partagé ne soit pas passé par référence.

#include <boost/shared_ptr.hpp>

class Base { };
class Derived: public Base { };

// ONLY instances of Base can be passed by reference.  If you have a shared_ptr
// to a derived type, you have to cast it manually.  If you remove the reference
// and pass the shared_ptr by value, then the cast is implicit so you don't have
// to worry about it.
void test(boost::shared_ptr<Base>& b)
{
    return;
}

int main(void)
{
    boost::shared_ptr<Derived> d(new Derived);
    test(d);

    // If you want the above call to work with references, you will have to manually cast
    // pointers like this, EVERY time you call the function.  Since you are creating a new
    // shared pointer, you lose the benefit of passing by reference.
    boost::shared_ptr<Base> b = boost::dynamic_pointer_cast<Base>(d);
    test(b);

    return 0;
}
2
répondu Malvineous 2014-09-25 22:56:21

je supposerai que vous êtes familier avec optimisation prématurée et que vous le demandez soit à des fins académiques, soit parce que vous avez isolé un code préexistant qui est sous-performant.

le Passage par référence est d'accord

passer par la référence const est préférable, et peut généralement être utilisé, car il ne force pas la const-nance sur l'objet pointé.

vous êtes pas au risque de perdre le pointeur en raison de l'utilisation d'une référence. Cette référence est la preuve que vous avez une copie du pointeur intelligent plus tôt dans la pile et qu'un seul thread possède une pile d'appels, de sorte que cette copie préexistante ne disparaît pas.

en utilisant des références est souvent plus efficace pour les raisons que vous mentionnez, mais pas garanti . Rappelez-vous que déréférencier un objet peut prendre du travail aussi. Votre scénario de référence idéal serait si votre style de codage implique de nombreuses petites fonctions, où le pointeur serait passé d'une fonction à l'autre avant d'être utilisé.

Vous devriez toujours éviter de stocker les votre pointeur intelligent comme référence. Votre exemple Class::take_copy_of_sp(&sp) montre l'usage correct pour cela.

1
répondu Drew Dormann 2008-11-30 16:00:36

en supposant que nous ne sommes pas concernés par l'exactitude de const (ou plus, vous voulez dire permettre aux fonctions d'être en mesure de modifier ou de partager la propriété des données transmises), en passant un boost::shared_ptr par la valeur est plus sûr que de le passer par référence que nous permettons le boost original::shared_ptr pour contrôler sa propre vie. Considérez les résultats du code suivant...

void FooTakesReference( boost::shared_ptr< int > & ptr )
{
    ptr.reset(); // We reset, and so does sharedA, memory is deleted.
}

void FooTakesValue( boost::shared_ptr< int > ptr )
{
    ptr.reset(); // Our temporary is reset, however sharedB hasn't.
}

void main()
{
    boost::shared_ptr< int > sharedA( new int( 13 ) );
    boost::shared_ptr< int > sharedB( new int( 14 ) );

    FooTakesReference( sharedA );

    FooTakesValue( sharedB );
}

de l'exemple ci-dessus nous voyons que le passage sharedA par la référence permet à FooTakesReference de réinitialiser le pointeur d'origine, ce qui réduit son compte d'utilisation à 0, détruisant ses données. FooTakesValue , cependant, ne peut pas réinitialiser le pointeur d'origine, garantissant sharedB données est encore utilisable. Quand un autre développeur vient inévitablement et tente de piggyback sur shareda's existence fragile, le chaos s'ensuit. Le chanceux sharedB développeur, cependant, rentre à la maison tôt que tout est juste dans son monde.

la sécurité du code, dans ce cas, l'emporte largement sur toute amélioration de la vitesse de copie crée. Dans le même temps, le boost::shared_ptr est destiné à améliorer la sécurité du code. Il sera beaucoup plus facile de passer d'une copie à une référence, si quelque chose nécessite ce genre d'optimisation de niche.

1
répondu Kit10 2010-03-03 14:24:51

Sandy a écrit: "il semble que tous les pour et les contre ici peuvent effectivement être généralisées à N'importe quel type passé par référence pas seulement shared_ptr."

vrai dans une certaine mesure, mais le but d'utiliser shared_ptr est d'éliminer les préoccupations concernant les durées de vie des objets et de laisser le compilateur gérer cela pour vous. Si vous allez passer un pointeur partagé par référence et permettre aux clients de votre référence-counted-object call des méthodes non-const qui pourraient libérer les données de l'objet, alors utiliser un pointeur partagé est presque inutile.

j'ai écrit" presque "dans cette phrase précédente parce que la performance peut être une préoccupation, et il "pourrait" être justifié dans de rares cas, mais je voudrais également éviter ce scénario moi-même et de chercher toutes les autres solutions d'optimisation possibles moi-même, tels que d'envisager sérieusement d'ajouter un autre niveau d'évaluation indirecte, paresseuse, etc..

Code qui existe au-delà de son auteur, ou même post it's author's memory, que exige des suppositions implicites sur le comportement, en particulier le comportement sur la durée de vie de l'objet, exige une documentation claire, concise et lisible, et puis beaucoup de clients ne le liront pas de toute façon! La simplicité l'emporte presque toujours sur l'efficacité, et il y a presque toujours d'autres moyens pour être efficace. Si vous avez vraiment besoin de passer des valeurs par référence pour éviter la copie en profondeur par les constructeurs de copie de vos objets de référence-comptés (et l'opérateur égal), alors peut-être vous devriez envisager des moyens de faire le profonde copié les données de référence compté pointeurs qui peuvent être copiés rapidement. (Bien sûr, ce n'est qu'un scénario de conception qui pourrait ne pas s'appliquer à votre situation).

1
répondu Bill H 2011-09-02 19:46:53

j'ai l'habitude de travailler dans un projet que le principe était très fort au sujet de passer des pointeurs intelligents par valeur. Quand on m'a demandé de faire une analyse de performance - j'ai trouvé que pour l'incrément et le décrément des compteurs de référence des pointeurs intelligents l'application dépense entre 4-6% du temps de processeur utilisé.

si vous voulez passer les pointeurs intelligents par la valeur juste pour éviter d'avoir des problèmes dans les cas bizarres comme décrit par Daniel Earwicker assurez-vous que vous comprendre le prix que vous payez pour cela.

si vous décidez d'utiliser une référence, la principale raison d'utiliser la référence const est de permettre une upcasting implicite lorsque vous avez besoin de passer shared pointer pour objecter à partir de la classe qui hérite de la classe que vous utilisez dans l'interface.

1
répondu gsf 2013-04-19 18:43:03

en plus de ce que litb a dit, j'aimerais souligner que c'est probablement passer par la référence de const dans le deuxième exemple, de cette façon vous êtes sûr que vous ne le modifiez pas accidentellement.

0
répondu Leon Timmermans 2008-11-29 17:36:21
struct A {
  shared_ptr<Message> msg;
  shared_ptr<Message> * ptr_msg;
}
  1. passage par valeur:

    void set(shared_ptr<Message> msg) {
      this->msg = msg; /// create a new shared_ptr, reference count will be added;
    } /// out of method, new created shared_ptr will be deleted, of course, reference count also be reduced;
    
  2. passer par la référence:

    void set(shared_ptr<Message>& msg) {
     this->msg = msg; /// reference count will be added, because reference is just an alias.
     }
    
  3. passage par pointeur:

    void set(shared_ptr<Message>* msg) {
      this->ptr_msg = msg; /// reference count will not be added;
    }
    
0
répondu Dylan Chen 2013-07-23 07:42:53

chaque morceau de code doit porter un certain sens. Si vous passez un pointeur partagé par valeur everywhere dans la demande, cela signifie "Je ne suis pas sûr de ce qui se passe ailleurs, donc je préfère la sécurité brute ". Ce n'est pas ce que j'appelle un bon signe de confiance pour les autres programmeurs qui pourraient consulter le code.

de toute façon, même si une fonction obtient une référence const et que vous êtes "incertain", vous pouvez toujours créer une copie de la pointeur à la tête de la fonction, pour ajouter une référence forte au pointeur. Cela pourrait aussi être considéré comme une indication sur le dessin ("le pointeur pourrait être modifié ailleurs").

Donc, oui, l'OMI, la valeur par défaut doit être " passer par référence const ".

0
répondu Philippe 2016-03-05 07:45:33