Dans le C ++ traditionnel, le passage par valeur dans les fonctions et les méthodes est lent pour les objets volumineux et est généralement mal vu. Au lieu de cela, les programmeurs C ++ ont tendance à passer des références, ce qui est plus rapide, mais qui introduit toutes sortes de questions compliquées autour de la propriété et en particulier de la gestion de la mémoire (dans le cas où l'objet est alloué en 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 d'implémenter un objet volumineux (comme un std::vector
) qui est bon marché à passer par valeur dans et hors d'une fonction.
Alors, cela signifie-t-il que la valeur par défaut devrait être de passer par valeur pour les instances de types tels que std::vector
et std::string
? Qu'en est-il des objets personnalisés? Quelle est la nouvelle meilleure pratique?
la source
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 en quoi c'est compliqué ou problématique pour la propriété? Peut-être ai-je raté quelque chose?const std::string&
une copie et non une copie. Le premier fil est ensuite sorti ...Réponses:
C'est une valeur par défaut raisonnable si vous devez faire une copie à l'intérieur du corps. C'est ce que préconise Dave Abrahams :
Dans le code, cela signifie ne pas faire ceci:
mais faites ceci:
qui a l'avantage que l'appelant peut utiliser
foo
comme ceci:et seul un travail minimal est effectué. Vous auriez besoin de deux surcharges pour faire la même chose avec les références,
void foo(T const&);
etvoid foo(T&&);
.Dans cet esprit, j'ai maintenant écrit mes précieux constructeurs en tant que tels:
Sinon, passer par référence à
const
still est raisonnable.la source
SomeProperty p;
for (auto x: vec) { x.foo(p); }
ne rentre pas, par exemple. De plus, les constructeurs de mouvements ont un coût (plus l'objet est grand, plus le coût est élevé) alors qu'ilsconst&
sont essentiellement gratuits.std::vector
avec un million d'éléments coûte le même prix qu'en déplacer un avec cinq éléments puisque seul le pointeur vers le tableau sur le tas est déplacé, pas tous les objets du vecteur. Ce n'est donc pas vraiment un gros problème.std::move
partout ..const&
, qui m'a fait trébucher à plusieurs reprises.void foo(const T&); int main() { S s; foo(s); }
. Cela peut compiler, même si les types sont différents, s'il existe un constructeur T qui prend un S comme argument. Cela peut être lent, car un grand objet T peut être construit. Vous pensez peut-être que vous passez une référence sans copier, mais vous pouvez l'être. Voir cette réponse à une question que j'ai demandée plus. Fondamentalement,&
se lie généralement uniquement aux valeurs l, mais il existe une exception pourrvalue
. Il existe des alternatives.Dans presque tous les cas, votre sémantique devrait être soit:
Toutes les autres signatures ne doivent être utilisées qu'avec parcimonie et avec une bonne justification. Le compilateur les travaillera désormais à peu près toujours de la manière la plus efficace. Vous pouvez simplement continuer à écrire votre code!
la source
foo(bar& x) { x.a = 3; }
Est beaucoup plus fiable (et lisible!)foo(bar* x) {if (!x) throw std::invalid_argument("x"); x->a = 3;
ref
mot - clé en C #).is shared_ptr intended to never be null? Much as (I think) unique_ptr is?
Ces deux hypothèses sont incorrectes.unique_ptr
etshared_ptr
peut contenir desnullptr
valeurs nulles / . Si vous ne voulez pas vous soucier des valeurs nulles, vous devez utiliser des références, car elles ne peuvent jamais être nulles. Vous n'aurez pas non plus à taper->
, ce que vous trouvez ennuyeux :)Passez les paramètres par valeur si, à l'intérieur du corps de la fonction, vous avez besoin d'une copie de l'objet ou que vous avez seulement besoin de déplacer l'objet. Passez
const&
si vous n'avez besoin que d'un accès non mutant à l'objet.Exemple de copie d'objet:
Exemple de déplacement d'objet:
Exemple d'accès sans mutation:
Pour en savoir plus, consultez ces articles de blog de Dave Abrahams et Xiang Fan .
la source
La signature d'une fonction doit refléter son utilisation prévue. La lisibilité est importante, également pour l'optimiseur.
C'est la meilleure condition préalable pour qu'un optimiseur crée le code le plus rapide - en théorie du moins et sinon en réalité, dans quelques années la réalité.
Les considérations de performances sont très souvent surestimées dans le contexte du passage de paramètres. La transmission parfaite en est un exemple. Les fonctions comme
emplace_back
sont pour la plupart très courtes et intégrées de toute façon.la source