Le but d'une shared_ptr
instance distincte est de garantir (dans la mesure du possible) que tant que cela shared_ptr
est 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_ptr
sur ce même objet? Et si c'était le seul restant distinct shared_ptr
de 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:
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.
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::string
qui est probablement copié lorsqu'il est transmis à plusieurs endroits, nous utilisons un shared_ptr
dans 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_message
est 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_message
tenter 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 msg
n'est jamais nul lorsqu'il send_message
est 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_ptr
continu à ê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_ptr
n'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_ptr
en 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::string
partout 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.