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

115

Si j'ai une fonction qui doit fonctionner avec a shared_ptr, ne serait-il pas plus efficace de lui passer une référence (pour éviter de copier l' shared_ptrobjet)? Quels sont les effets secondaires possibles? J'envisage deux cas possibles:

1) à l'intérieur de la fonction, une copie de l'argument est faite, 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 est uniquement utilisé, comme dans

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

Je ne vois pas dans les deux cas une bonne raison de passer la boost::shared_ptr<foo>valeur par valeur plutôt que par référence. Le passage par valeur n'incrémenterait que "temporairement" le nombre de références en raison de la copie, puis le décrémenterait lors de la sortie de la portée de la fonction. Est-ce que je néglige 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 de profiler d'abord puis de travailler sur les hotspots. Ma question portait davantage sur un code purement technique, si vous voyez ce que je veux dire.

Abigagli
la source
Je ne sais pas si vous pouvez modifier les balises de votre question, mais essayez d'y ajouter une balise boost. J'ai essayé de chercher cette question mais je n'en ai pas trouvé parce que je cherchais des balises boost et smart-pointer. J'ai donc trouvé votre question juste après avoir composé ma propre question
Edison Gustavo Muenz

Réponses:

113

Le but d'une shared_ptrinstance distincte est de garantir (dans la mesure du possible) que tant que cela shared_ptrest dans la portée, l'objet vers lequel elle pointe existera toujours, car son nombre de références 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
}

Ainsi, en utilisant une référence à a shared_ptr, vous désactivez cette 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()cela ne va pas exploser en raison d'un pointeur nul?

Tout dépend de ce qu'il y a dans ces sections «...» du code. Que faire si vous appelez quelque chose pendant le premier «...» qui a pour effet secondaire (quelque part dans une autre partie du code) d'effacer un shared_ptrsur ce même objet? Et si c'était le seul restant distinct shared_ptrde cet objet? Bye bye object, 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 l'ensemble de votre programme jusqu'à ce que vous soyez sûr que l'objet ne mourra pas pendant le corps de la fonction.

  2. Remplacez le paramètre par un objet distinct au lieu d'une référence.

Conseil général qui s'applique ici: ne vous souciez pas d'apporter des modifications risquées à votre code pour des raisons de performances jusqu'à ce que vous ayez chronométré votre produit dans une situation réaliste dans un profileur et que vous ayez mesuré de manière concluante que le changement que vous souhaitez apporter fera un différence significative de 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 car elle est cachée dans des couches de détails réels.

Nous avons une fonction qui enverra un message quelque part. Il peut s'agir d'un message volumineux, donc plutôt que d'utiliser un std::stringqui est probablement copié lorsqu'il est transmis à plusieurs endroits, nous utilisons un shared_ptrdans une chaîne:

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 fonction pour se souvenir du message précédent. Nous voulons le comportement suivant: il doit exister une variable contenant le message le plus récemment envoyé, mais pendant qu'un message est en cours d'envoi, il ne doit y avoir aucun 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;

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

Ainsi, avant de commencer à envoyer, nous rejetons le message précédent actuel, puis une fois l'envoi terminé, nous pouvons stocker le nouveau message précédent. 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 M. Maintainer, qui regarde le code et pense: Hé, ce paramètre send_messageest un shared_ptr:

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

Évidemment, cela peut être changé en:

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

Pensez à l'amélioration des performances que cela apportera! (Peu importe que nous soyons sur le point d'envoyer un message généralement volumineux sur un canal, l'amélioration des performances sera donc si petite qu'elle ne sera pas mesurable).

Mais le vrai problème est que le code de test présentera désormais un comportement indéfini (dans les versions de débogage Visual C ++ 2010, il se bloque).

M. Maintainer est surpris par cela, mais ajoute un test défensif pour send_messagetenter d'arrêter le problème:

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

Mais bien sûr, il continue et plante, car il msgn'est jamais nul lorsqu'il send_messageest appelé.

Comme je l'ai dit, avec tout le code si rapproché 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 détiennent des pointeurs les uns vers les 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 simple, où vous voulez qu'une fonction puisse s'appuyer sur un shared_ptrcontinu à être non nul partout, est que la fonction alloue son propre vrai shared_ptr, plutôt que de s'appuyer sur une référence à un existant shared_ptr.

L'inconvénient est que a copié shared_ptrn'est pas gratuit: même les implémentations "sans verrouillage" doivent utiliser une opération interlocked pour honorer les garanties de thread. Il peut donc y avoir des situations où un programme peut être considérablement accéléré en changeant a shared_ptren a shared_ptr &. Mais ce n'est pas un changement qui peut être apporté en toute sécurité à tous les programmes. Cela change la signification logique du programme.

Notez qu'un bug similaire se produirait si nous utilisions std::stringpartout au lieu de std::shared_ptr<std::string>et au lieu de:

previous_message = 0;

pour effacer le message, nous avons dit:

previous_message.clear();

Le symptôme serait alors l'envoi accidentel d'un message vide, au lieu d'un 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 copie d'un shared_ptr, donc le compromis peut être différent.

Daniel Earwicker
la source
16
Le shared_ptr qui est passé dans vit déjà dans une étendue, sur le site d'appel. Vous pourrez peut-être créer un scénario élaboré où le code de cette question exploserait en raison d'un pointeur suspendu, mais je suppose que vous avez des problèmes plus importants que le paramètre de référence!
Magnus Hoff
10
Il peut être stocké dans un membre. Vous pouvez appeler quelque chose qui arrive pour effacer ce membre. L'intérêt de smart_ptr est d'éviter d'avoir à coordonner les durées de vie dans des hiérarchies ou des étendues qui s'emboîtent autour de la pile d'appels, c'est pourquoi il est préférable de supposer que les durées de vie ne le font pas dans de tels programmes.
Daniel Earwicker
7
Ce n'est pas vraiment mon point de vue cependant! Si vous pensez que ce que je dis est quelque chose de spécifique à voir avec mon code, vous ne m'avez peut-être pas compris. Je parle d'une implication inévitable de la raison pour laquelle shared_ptr existe en premier lieu: de nombreuses durées de vie d'objet ne sont pas simplement liées aux appels de fonction.
Daniel Earwicker
8
@DanielEarwicker est entièrement d'accord avec tous vos points et je suis surpris par le niveau d'opposition. Quelque chose qui rend vos préoccupations encore plus pertinentes est le threading, lorsque cela devient impliqué, les garanties sur la validité d'un objet deviennent beaucoup plus importantes. Bonne réponse.
radman
3
Il n'y a pas longtemps, j'ai chassé un bogue très sérieux dû à la transmission d'une référence à un pointeur partagé. Le code gérait le changement d'état d'un objet et lorsqu'il a remarqué que l'état de l'objet avait changé, il l'a retiré de la collection d'objets dans l'état précédent et l'a déplacé dans la collection d'objets dans le nouvel état. L'opération de suppression a détruit le dernier pointeur partagé vers l'objet. La fonction membre avait été appelée sur une référence au pointeur partagé dans la collection. Boom. Daniel Earwicker a raison.
David Schwartz
115

Je me suis retrouvé en désaccord avec la réponse la plus votée, alors j'ai cherché des opinions d'experts et les voici. De http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2011-Scott-Andrei-and-Herb-Ask-Us-Anything

Herb Sutter: "lorsque vous passez shared_ptrs, les copies coûtent cher"

Scott Meyers: "Shared_ptr n'a rien de spécial quand il s'agit de savoir si vous le transmettez par valeur ou par référence. Utilisez 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 que c'est petit, il est forcément peu coûteux de passer par valeur. Il faut le copier, et il y a un coût associé à ça ... c'est cher de le passer par valeur, donc si je peux m'en tirer avec une sémantique appropriée dans mon programme, je vais le passer par référence à const ou référence à la place "

Herb Sutter: "passez-les toujours par référence à const, et très occasionnellement peut-être parce que vous savez ce que vous avez appelé pourrait modifier la chose dont vous avez obtenu une référence, peut-être que vous pourriez alors passer par valeur ... si vous les copiez en tant que paramètres, oh mon Dieu, vous n’avez presque jamais besoin d’augmenter ce nombre de références car il est de toute façon maintenu en vie, et vous devriez le passer par référence, alors faites-le. "

Mise à jour: Herb a développé ceci ici: http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/ , bien que la morale de l'histoire soit que vous ne devriez pas passer shared_ptrs at all "à moins que vous ne souhaitiez utiliser ou manipuler le pointeur intelligent lui-même, par exemple pour partager ou transférer la propriété."

Jon
la source
8
Bonne trouvaille! C'est formidable de voir deux des plus grands experts sur le sujet réfuter publiquement la sagesse conventionnelle sur l'OS.
Stephan Tolksdorf le
3
"Il n'y a rien de spécial à propos de shared_ptr quand il s'agit de savoir si vous le passez par valeur ou par référence" - Je ne suis vraiment pas d'accord avec cela. C'est spécial. Personnellement, je préfère jouer la carte de la sécurité et prendre le léger coup de performance. S'il y a une zone de code particulière que je dois optimiser, alors bien sûr, je regarderais les avantages de performance de shared_ptr pass by const ref.
JasonZ
3
Il est également intéressant de noter que, bien qu'il y ait eu accord sur l'utilisation abusive des shared_ptrs, il n'y avait pas d'accord sur la question du passage par valeur par rapport à la référence.
Nicol Bolas
2
Scott Meyers: "donc si je peux m'en tirer avec une sémantique appropriée dans mon programme ..." c'est-à-dire ne contredit pas du tout ma réponse, ce qui souligne que déterminer si le changement de paramètres const &affectera la sémantique n'est facile que dans très programmes simples.
Daniel Earwicker
7
Herb Sutter: "très occasionnellement peut-être parce que vous savez ce que vous appelez pourrait modifier la chose dont vous avez obtenu une référence". Encore une fois, cette petite exemption pour un cas mineur, donc cela ne contredit pas ma réponse. La question demeure: comment savoir qu'il est sûr d'utiliser une référence const? Très facile à prouver dans un programme simple, pas si facile dans un programme complexe. Mais bon, c'est du C ++, et nous favorisons donc la micro-optimisation prématurée par rapport à presque toutes les autres préoccupations d'ingénierie, non?! :)
Daniel Earwicker
22

Je déconseillerais cette pratique à moins que vous et les autres programmeurs avec lesquels vous travaillez vraiment, vraiment savoir ce que vous faites tous.

Premièrement, vous n'avez aucune idée de la façon dont l'interface avec 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, car ce n'est pas idiomatique, et cela facilite son utilisation incorrecte. Programmez de manière défensive: rendez l'interface difficile à utiliser de manière incorrecte. Passer par référence ne fera qu'inviter des problèmes plus tard.

Deuxièmement, n'optimisez pas jusqu'à ce que vous sachiez que cette classe particulière va être un problème. Profil d'abord, et ensuite si votre programme a vraiment besoin du coup de pouce donné en passant par référence, alors peut-être. Sinon, ne vous inquiétez pas pour les petites choses (c'est-à-dire les N instructions supplémentaires qu'il faut pour passer par valeur) au lieu de vous soucier de la conception, des structures de données, des algorithmes et de la maintenabilité à long terme.

Mark Kegel
la source
Bien que la réponse de litb soit techniquement correcte, ne sous-estimez jamais la «paresse» des programmeurs (je suis aussi paresseux!). La réponse de littlenag est meilleure, qu'une référence à shared_ptr sera inattendue, et peut-être (probablement) une optimisation inutile qui rendra la maintenance future plus difficile.
netjeff
18

Oui, prendre une référence est bien là-bas. Vous n'avez pas l'intention de partager la propriété de la méthode; il veut seulement travailler avec. Vous pouvez également prendre une référence pour le premier cas, puisque vous le copiez quand même. Mais pour le premier cas, il faut la propriété. Il y a cette astuce pour le copier encore 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 renvoyez (c'est-à-dire ne pas renvoyer une référence). Parce que votre classe ne sait pas ce que le client en fait (elle pourrait stocker un pointeur vers elle et puis un big bang se produira). S'il s'avère plus tard qu'il s'agit d'un goulot d'étranglement (premier profil!), Vous pouvez toujours renvoyer une référence.


Edit : Bien sûr, comme d'autres le soulignent, cela n'est vrai que si vous connaissez votre code et savez que vous ne réinitialisez pas le pointeur partagé transmis d'une manière ou d'une autre. En cas de doute, passez simplement par valeur.

Johannes Schaub - litb
la source
11

Il est judicieux de passer shared_ptrs const&. Cela ne causera probablement pas de problèmes (sauf dans le cas peu probable où le référencé shared_ptrest supprimé pendant l'appel de fonction, comme détaillé par Earwicker) et ce sera probablement plus rapide si vous en passez beaucoup. Rappelles toi; la valeur par défaut boost::shared_ptrest thread safe, donc sa copie inclut un incrément thread safe.

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

Magnus Hoff
la source
3
Oui, j'utilise toujours des références const, j'ai juste oublié de le mettre dans mon exemple. Quoi qu'il en soit, MSVC permet de lier des références non const à des temporaires non pas pour un bogue, mais parce que par défaut, il a la propriété "C / C ++ -> Langage -> Désactiver l'extension de langage" définie sur "NON". Activez-le et il ne les compilera pas ...
abigagli
abigagli: Sérieusement? Doux! Je vais appliquer cela au travail, à la première heure demain;)
Magnus Hoff
10

Dans le second cas, cela est plus simple:

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

Vous pouvez l'appeler comme

only_work_with_sp(*sp);
Greg Rogers
la source
3
Si vous adoptez la convention pour utiliser des références d'objet lorsque vous n'avez pas besoin de prendre une copie du pointeur, cela sert également à documenter votre intention. Cela vous donne également la possibilité d'utiliser une référence const.
Mark Ransom
Oui, je suis d'accord sur l'utilisation de références aux objets comme moyen d'exprimer que la fonction appelée ne "se souvient" de rien de cet objet. Habituellement, j'utilise des arguments formels de pointeur si la fonction "garde la trace" de l'objet
abigagli
3

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

A const &peut être une micro-optimisation sensible lors de l'appel de petites fonctions - par exemple pour permettre d'autres optimisations, comme l'inclusion de certaines conditions. De plus, l'incrémentation / décrémentation - puisqu'elle est thread-safe - est un point de synchronisation. Cependant, je ne m'attendrais pas à ce que cela fasse une grande différence dans la plupart des scénarios.

En règle générale, vous devez utiliser le style le plus simple, sauf si vous avez des raisons de ne pas le faire. Ensuite, utilisez le de manière const &cohérente ou ajoutez un commentaire expliquant pourquoi si vous ne l'utilisez qu'à quelques endroits.

Peterchen
la source
3

Je recommanderais de passer un pointeur partagé par référence const - une sémantique selon laquelle la fonction passée avec le pointeur ne possède PAS le pointeur, qui est un idiome propre pour les développeurs.

Le seul inconvénient est que dans plusieurs programmes de thread, l'objet pointé par le pointeur partagé est détruit dans un autre thread. Il est donc prudent de dire que l'utilisation de la référence const du pointeur partagé est sûre dans un programme à thread unique.

Passer un pointeur partagé par une référence non-const est parfois dangereux - la raison en est les fonctions d'échange et de réinitialisation que la fonction peut invoquer à l'intérieur afin de détruire l'objet qui est toujours 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 de cycles de processeur lorsque vous savez clairement ce que vous voulez faire et que l'idiome de codage a été fermement adopté par vos collègues développeurs.

Juste mes 2 cents :-)

zmqiu
la source
1
Voir le commentaire ci-dessus de David Schwartz "... J'ai chassé un bogue très sérieux qui était dû au passage d'une référence à un pointeur partagé. Le code gérait le changement d'état d'un objet et quand il a remarqué que l'état de l'objet avait changé, il l'a supprimé de la collection d'objets dans l'état précédent et l'a déplacé dans la collection d'objets dans le nouvel état. L'opération de suppression a détruit le dernier pointeur partagé vers l'objet. La fonction membre avait été appelée sur une référence au pointeur partagé dans la collection. Boom ... "
Jason Harrison
3

Il semble que tous les avantages et inconvénients ici peuvent en fait être généralisés à N'IMPORTE QUEL type passé par référence et pas seulement shared_ptr. À mon avis, vous devez connaître la sémantique du passage par référence, référence const et valeur et l'utiliser correctement. Mais il n'y a absolument rien de mal en soi à passer shared_ptr par référence, à moins que vous ne pensiez 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 sais-tu ça sp.do_something() cela ne va pas exploser à cause d'un pointeur suspendu?

La vérité est que, shared_ptr ou non, const ou non, cela peut se produire si vous avez un défaut de conception, comme le partage direct ou indirect de la propriété spentre les threads, l'utilisation incorrecte d'un objet delete this, vous avez une propriété circulaire ou d'autres erreurs de propriété.

Sablonneux
la source
2

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 souhaitez passer un pointeur partagé de classe dérivée via une référence à un pointeur partagé de classe de base.

Par exemple, ce code produira une erreur, mais il fonctionnera si vous modifiez test()afin 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;
}
Malvineous
la source
1

Je suppose que vous êtes familier avec l' optimisation prématurée et demandez à des fins académiques ou parce que vous avez isolé un code préexistant qui est sous-performant.

Passer par référence, c'est bien

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

Vous ne risquez pas de perdre le pointeur en raison de l'utilisation d'une référence. Cette référence prouve 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 la copie préexistante ne disparaît pas.

L'utilisation de références est souvent plus efficace pour les raisons que vous mentionnez, mais pas garantie . N'oubliez pas que déréférencer un objet peut également demander du travail. Votre scénario idéal d'utilisation de référence serait si votre style de codage implique de nombreuses petites fonctions, où le pointeur serait passé de fonction en fonction à fonction avant d'être utilisé.

Vous devez toujours éviter de stocker votre pointeur intelligent comme référence. Votre Class::take_copy_of_sp(&sp)exemple montre une utilisation correcte pour cela.

Drew Dormann
la source
1
"Vous ne risquez pas 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" Ou un membre de données ...?
Daniel Earwicker
Considérez la magie de boost :: thread et boost :: ref: boost :: function <int> functionPointer = boost :: bind (doSomething, boost :: ref (sharedPtrInstance)); m_workerThread = nouveau boost :: thread (functionPointer); ... supprimer sharedPtrInstance
Jason Harrison
1

En supposant que nous ne soyons pas concernés par l'exactitude de la const (ou plus, vous voulez permettre aux fonctions de pouvoir modifier ou partager la propriété des données transmises), transmettre un boost :: shared_ptr par valeur est plus sûr que de le passer par référence comme nous permettons à l'original boost :: shared_ptr de contrôler sa propre durée de 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 );
}

Dans l'exemple ci-dessus, nous voyons que le passage de sharedA par référence permet à FooTakesReference de réinitialiser le pointeur d'origine, ce qui réduit son nombre d'utilisation à 0, détruisant ses données. FooTakesValue , cependant, ne peut pas réinitialiser le pointeur d'origine, garantissant que les données de sharedB sont toujours utilisables. Quand un autre développeur arrive inévitablement et tente de s'appuyer sur l' existence fragile de sharedA , le chaos s'ensuit. Cependant, l' heureux développeur sharedB rentre chez lui tôt car tout va bien dans son monde.

La sécurité du code, dans ce cas, l'emporte de loin sur toute amélioration de la vitesse créée par la copie. En 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.

Kit10
la source
1

Sandy a écrit: "Il semble que tous les avantages et inconvénients ici peuvent en fait être généralisés à N'IMPORTE QUEL type passé par référence et pas seulement shared_ptr."

C'est vrai dans une certaine mesure, mais le but d'utiliser shared_ptr est d'éliminer les problèmes concernant la durée 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 objet de référence d'appeler des méthodes non const qui pourraient libérer les données de l'objet, alors l'utilisation d'un pointeur partagé est presque inutile.

J'ai écrit «presque» dans cette phrase précédente parce que les performances peuvent être un problème, et cela «pourrait» être justifié dans de rares cas, mais j'éviterais également ce scénario moi-même et chercherais moi-même toutes les autres solutions d'optimisation possibles, comme regarder sérieusement à l'ajout d'un autre niveau d'indirection, d'évaluation paresseuse, etc.

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

Bill H
la source
1

J'avais l'habitude de travailler dans un projet dont le principe était très fort de transmettre des pointeurs intelligents par valeur. Lorsqu'on m'a demandé de faire une analyse des performances - j'ai constaté que pour l'incrémentation et la décrémentation des compteurs de référence des pointeurs intelligents, l'application passe entre 4 et 6% du temps de processeur utilisé.

Si vous voulez passer les pointeurs intelligents par valeur juste pour éviter d'avoir des problèmes dans des cas étranges comme décrit par Daniel Earwicker, assurez-vous de 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 rendre possible la conversion ascendante implicite lorsque vous devez transmettre un pointeur partagé à un objet de la classe qui hérite de la classe que vous utilisez dans l'interface.

gsf
la source
0

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

Léon Timmermans
la source
0
struct A {
  shared_ptr<Message> msg;
  shared_ptr<Message> * ptr_msg;
}
  1. passer 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 référence:

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

    void set(shared_ptr<Message>* msg) {
      this->ptr_msg = msg; /// reference count will not be added;
    }
Dylan Chen
la source
0

Chaque morceau de code doit avoir un sens. Si vous passez un pointeur partagé par valeur partout dans l'application, cela signifie "Je ne suis pas sûr de ce qui se passe ailleurs, donc je privilégie 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.

Quoi qu'il en soit, même si une fonction obtient une référence const et que vous n'êtes "pas sûr", vous pouvez toujours créer une copie du pointeur partagé en tête de la fonction, pour ajouter une référence forte au pointeur. Cela pourrait également être vu comme un indice sur la conception ("le pointeur pourrait être modifié ailleurs").

Alors oui, OMI, la valeur par défaut devrait être " passer par référence const ".

Philippe
la source