Est passé par valeur raisonnable par défaut en C++11?
Traditionnelle C++, en passant par valeur dans les fonctions et les méthodes est lent pour des objets de grande taille, et est généralement mal vu. Au lieu de cela, les programmeurs en C++ ont tendance à passer des références, ce qui est plus rapide, mais qui présente toutes sortes de questions complexes autour de la propriété et plus particulièrement autour de la gestion de la mémoire (dans le cas où l'objet est alloué par tas)
Maintenant, en C++11, nous avons des références Rvalue et des constructeurs de déplacement, ce qui signifie qu'il est possible de mettre en œuvre un objet de grande taille (comme un std::vector
) ce n'est pas cher pour passer par valeur dans et hors d'une fonction.
Donc, est-ce à dire que la valeur par défaut doit être de passer par valeur pour les instances de types tels que std::vector
et std::string
? Ce sujet pour les objets personnalisés? Quelle est la meilleure pratique?
pass by reference ... which introduces all sorts of complicated questions around ownership and especially around memory management (in the event that the object is heap-allocated)
. Je ne comprends pas comment c'est compliqué ou difficile pour la propriété? Peut-être que j'ai manqué quelque chose ?- Un exemple tiré de l'expérience personnelle. Un thread a un objet de type string. Il est transmis à une fonction qui génère un autre thread, mais inconnu à l'appelant la fonction a pris la chaîne comme
const std::string&
et non une copie. Le premier thread puis quitté... - Qui sonne comme une fonction clairement jamais conçu pour être appelée comme une fonction de thread.
- D'accord avec iammilind, je ne vois pas de problème. En passant par la const de référence par défaut pour les "gros" objets, et par la valeur pour les plus petits objets. Je l'avais mis de la limite entre les grands et les petits à autour de 16 octets (ou 4 pointeurs sur un système 32 bits).
- Herb Sutter est Retour à l'essentiel! Essentials of Modern C++ présentation à CppCon suis allé dans pas mal de détail sur cette. Vidéo.
- Connexes: Comment passer des objets à des fonctions en C++?
Vous devez vous connecter pour publier un commentaire.
C'est raisonnable par défaut si vous avez besoin de faire une copie à l'intérieur du corps. C'est ce que Dave Abrahams préconise:
Dans le code, cela signifie ne pas faire ceci:
mais ce faire:
qui a l'avantage que l'appelant
foo
comme suit:et seulement un minimum de travail est fait. Vous auriez besoin de deux surcharges de faire de même avec les références,
void foo(T const&);
etvoid foo(T&&);
.Avec cela à l'esprit, maintenant j'écrit mes chers constructeurs en tant que tel:
Sinon, le passage par référence à
const
est encore raisonnable.SomeProperty p;
for (auto x: vec) { x.foo(p); }
ne convient pas, par exemple. En outre, Déplacer les Constructeurs ont un coût (la plus grande de l'objet, plus le coût), tandis queconst&
sont essentiellement libres.std::vector
avec un million d'éléments les coûts de la même en tant que de déplacer l'un des cinq éléments puisque seul le pointeur vers le tableau sur le tas est déplacé, et non tous les objets dans le vecteur. Il n'est donc pas réellement que les grandes d'un problème.std::move
tous sur la place..const&
, qui a déclenché moi un peu de temps.void foo(const T&); int main() { S s; foo(s); }
. Cela permet de compiler, même si les types sont différents, si il y a un tee constructeur qui prend un S comme argument. Cela peut être long, car un grand T objet peut être se construit. Vous pouvez pense votre de passer une référence sans la copier, mais peut vous êtes. Voir cette réponse à une question que j'avais posée pour plus d'. Fondamentalement,&
habituellement ne se lie qu'à lvalues, mais il y a une exception pourrvalue
. Il existe des alternatives.const&
, lui permettant de se lier à des rvalues (par exemple, les copies temporaires)'Dans presque tous les cas, votre sémantique doit être soit:
Toutes les autres signatures doivent être utilisés qu'avec parcimonie, et avec une bonne justification. Le compilateur va maintenant presque toujours travailler dans la manière la plus efficace. Vous pouvez tout simplement écrire votre code!
foo(bar& x) { x.a = 3; }
est un diable de beaucoup plus fiable (et lisible!) quefoo(bar* x) {if (!x) throw std::invalid_argument("x"); x->a = 3;
ref
mot-clé en C#).out<>
pour les 'param'. Ensuite, la signature de la fonction seraitbar(out<foo> f)
au lieu debar(foo * f)
.->
au lieu de.
est ennuyeux 🙂 (en Fait, c'estshared_ptr
destiné à ne jamais être nulle? Bien que (je pense)unique_ptr
est?)unique_ptr
etshared_ptr
peut contenir null/nullptr
valeurs. Si vous ne voulez pas vous soucier de valeurs null, vous devriez être en utilisant des références, parce qu'ils ne peuvent jamais être null. En outre, vous ne pas avoir à taper->
, que vous trouverez à être ennuyeux 🙂void f(A& r) {} A* p = nullptr; /* result of some find, for example */ f(*p);
Passer des paramètres par valeur si à l'intérieur du corps de la fonction vous avez besoin d'une copie de l'objet ou seulement besoin de déplacer l'objet. Passer par
const&
si vous avez uniquement besoin de la non-mutation de l'accès à l'objet.Copie d'objet exemple:
Déplacement d'objet exemple:
Non-mutation d'accès exemple:
Pour la justification, voir ces billets de blog par Dave Abrahams et Xiang Fan.