Renvoie les pointeurs intelligents par valeur.
Comme vous l'avez dit, si vous le renvoyez par référence, vous n'incrémenterez pas correctement le nombre de références, ce qui risque de supprimer quelque chose au mauvais moment. Cela seul devrait être une raison suffisante pour ne pas revenir par référence. Les interfaces doivent être robustes.
Le problème de coût est de nos jours sans objet grâce à l' optimisation de la valeur de retour (RVO), vous n'encourrez donc pas une séquence d'incrémentation-incrémentation-décrémentation ou quelque chose du genre dans les compilateurs modernes. Donc, la meilleure façon de renvoyer a shared_ptr
est simplement de retourner par valeur:
shared_ptr<T> Foo()
{
return shared_ptr<T>(/* acquire something */);
};
C'est une opportunité RVO évidente pour les compilateurs C ++ modernes. Je sais pertinemment que les compilateurs Visual C ++ implémentent RVO même lorsque toutes les optimisations sont désactivées. Et avec la sémantique de déplacement de C ++ 11, cette préoccupation est encore moins pertinente. (Mais le seul moyen d'être sûr est de profiler et d'expérimenter.)
Si vous n'êtes toujours pas convaincu, Dave Abrahams a un article qui propose un retour par valeur. Je reproduis un extrait ici; Je vous recommande vivement d'aller lire l'article en entier:
Soyez honnête: que ressentez-vous avec le code suivant?
std::vector<std::string> get_names();
...
std::vector<std::string> const names = get_names();
Franchement, même si je devrais savoir mieux, cela me rend nerveux. En principe, lors des get_names()
retours, nous devons copier a vector
de l' string
art. Ensuite, nous devons le copier à nouveau lors de l'initialisation
names
, et nous devons détruire la première copie. S'il y a N string
s dans le vecteur, chaque copie peut nécessiter jusqu'à N + 1 allocations de mémoire et toute une série d'accès aux données non compatibles avec le cache> lorsque le contenu de la chaîne est copié.
Plutôt que d'affronter ce genre d'anxiété, je me suis souvent rabattu sur le passage par référence pour éviter les copies inutiles:
get_names(std::vector<std::string>& out_param );
...
std::vector<std::string> names;
get_names( names );
Malheureusement, cette approche est loin d'être idéale.
- Le code a augmenté de 150%
- Nous avons dû abandonner
const
-ness parce que nous mutons des noms.
- Comme les programmeurs fonctionnels aiment nous le rappeler, la mutation rend le code plus complexe à raisonner en sapant la transparence référentielle et le raisonnement équationnel.
- Nous n'avons plus de sémantique de valeur stricte pour les noms.
Mais est-il vraiment nécessaire de gâcher notre code de cette manière pour gagner en efficacité? Heureusement, la réponse s'avère non (et surtout pas si vous utilisez C ++ 0x).
cout << "Hello World!";
instruction dans un constructeur par défaut et de copie, vous ne verrez pas deuxHello World!
s lorsque RVO est en vigueur. Cependant, cela ne devrait pas être un problème pour les pointeurs intelligents correctement conçus, même pour la synchronisation.En ce qui concerne tout pointeur intelligent (pas seulement shared_ptr), je ne pense pas qu'il soit jamais acceptable de renvoyer une référence à un pointeur, et j'hésiterais beaucoup à les faire passer par référence ou pointeur brut. Pourquoi? Parce que vous ne pouvez pas être certain qu'il ne sera pas copié par la suite via une référence. Votre premier point définit la raison pour laquelle cela devrait être un problème. Cela peut se produire même dans un environnement à un seul thread. Vous n'avez pas besoin d'un accès simultané aux données pour mettre une sémantique de mauvaise copie dans vos programmes. Vous ne contrôlez pas vraiment ce que vos utilisateurs font avec le pointeur une fois que vous le faites passer, alors n'encouragez pas une mauvaise utilisation en donnant à vos utilisateurs d'API assez de corde pour se pendre.
Deuxièmement, regardez l'implémentation de votre pointeur intelligent, si possible. La construction et la destruction devraient être presque négligeables. Si cette surcharge n'est pas acceptable, n'utilisez pas de pointeur intelligent! Mais au-delà de cela, vous devrez également examiner l'architecture de concurrence que vous avez, car un accès mutuellement exclusif au mécanisme qui suit les utilisations du pointeur va vous ralentir plus que la simple construction de l'objet shared_ptr.
Edit, 3 ans plus tard: avec l'avènement des fonctionnalités plus modernes de C ++, je modifierais ma réponse pour être plus acceptant les cas où vous avez simplement écrit un lambda qui ne vit jamais en dehors de la portée de la fonction appelante, et qui n'est pas copié ailleurs. Ici, si vous vouliez économiser la surcharge très minimale de la copie d'un pointeur partagé, ce serait juste et sûr. Pourquoi? Parce que vous pouvez garantir que la référence ne sera jamais mal utilisée.
la source