Est-ce que pass-by-value est une valeur par défaut raisonnable en C ++ 11?

En C ++ traditionnel, le passage de la valeur aux fonctions et aux méthodes est lent pour les gros objects et est généralement mal vu. Au lieu de cela, les programmeurs C ++ ont tendance à faire circuler les références, ce qui est plus rapide, mais qui introduit toutes sortes de questions complexes concernant la propriété et en particulier la gestion de la mémoire (dans le cas où l’object est alloué).

Maintenant, en C ++ 11, nous avons des références Rvalue et des constructeurs de déplacement, ce qui signifie qu’il est possible d’implémenter un object de grande taille (comme un std::vector ) bon marché pour passer d’une valeur à une autre.

Donc, cela signifie-t-il que la valeur par défaut devrait être de passer par valeur pour les instances de types telles que std::vector et std::ssortingng ? Qu’en est-il des objects personnalisés? Quelle est la nouvelle meilleure pratique?

C’est un défaut raisonnable si vous devez faire une copie dans le corps. C’est ce que Dave Abrahams préconise :

Ligne direcsortingce: ne copiez pas vos arguments de fonction. Au lieu de cela, transmettez-les par valeur et laissez le compilateur faire la copie.

En code, cela signifie ne pas faire ceci:

 void foo(T const& t) { auto copy = t; // ... } 

mais fais ceci:

 void foo(T t) { // ... } 

qui a l’avantage que l’appelant peut utiliser foo comme ça:

 T lval; foo(lval); // copy from lvalue foo(T {}); // (potential) move from prvalue foo(std::move(lval)); // (potential) move from xvalue 

et seul un travail minimal est fait. Vous auriez besoin de deux surcharges pour faire la même chose avec les références, void foo(T const&); et void foo(T&&); .

Dans cet esprit, j’ai maintenant écrit mes précieux constructeurs en tant que tels:

 class T { U u; V v; public: T(U u, V v) : u(std::move(u)) , v(std::move(v)) {} }; 

Sinon, passer par référence à const est raisonnable.

Dans presque tous les cas, votre sémantique doit être soit:

 bar(foo f); // want to obtain a copy of f bar(const foo& f); // want to read f bar(foo& f); // want to modify f 

Toutes les autres signatures doivent être utilisées avec parcimonie et avec une bonne justification. Le compilateur fonctionnera à peu près toujours de la manière la plus efficace. Vous pouvez simplement continuer à écrire votre code!

Passez les parameters par valeur si, à l’intérieur du corps de la fonction, vous avez besoin d’une copie de l’object ou que seul l’object doit être déplacé. Passez par const& si vous avez uniquement besoin d’un access non-mutant à l’object.

Exemple de copie d’object:

 void copy_antipattern(T const& t) { // (Don't do this.) auto copy = t; t.some_mutating_function(); } void copy_pattern(T t) { // (Do this instead.) t.some_mutating_function(); } 

Exemple de déplacement d’object:

 std::vector v; void move_antipattern(T const& t) { v.push_back(t); } void move_pattern(T t) { v.push_back(std::move(t)); } 

Exemple d’access sans mutation:

 void read_pattern(T const& t) { t.some_const_function(); } 

Pour des raisons de logique, voir ces billets de blog par Dave Abrahams et Xiang Fan .