Est-il préférable en C ++ de passer par valeur ou de passer par référence constante?

Est-il préférable en C ++ de passer par valeur ou de passer par référence constante?

Je me demande quelle est la meilleure pratique. Je me rends compte que le passage par une référence constante devrait fournir de meilleures performances dans le programme parce que vous ne faites pas une copie de la variable.

Auparavant, il était généralement recommandé d’ utiliser la méthode cont by const pour tous les types , sauf pour les types intégrés ( char , int , double , etc.), pour les iterators et pour les objects fonction (lambdas, classes dérivant de std::*_function ).

C’était particulièrement vrai avant l’existence de la sémantique de mouvement . La raison est simple: si vous passez par valeur, une copie de l’object doit être faite et, à l’exception de très petits objects, cela coûte toujours plus cher que de passer une référence.

Avec C ++ 11, nous avons acquis une sémantique de mouvement . En bref, la sémantique de mouvement permet que, dans certains cas, un object puisse être transmis «par valeur» sans le copier. C’est notamment le cas lorsque l’object que vous passez est une valeur .

En soi, déplacer un object rest au moins aussi coûteux que le passage par référence. Cependant, dans de nombreux cas, une fonction copiera de manière interne un object, c’est-à-dire qu’elle prendra possession de l’argument. 2

Dans ces situations, nous avons le compromis suivant (simplifié):

  1. Nous pouvons passer l’object par référence, puis copier en interne.
  2. Nous pouvons passer l’object par valeur.

“Passer par valeur” provoque toujours la copie de l’object, sauf si l’object est une valeur. Dans le cas d’une valeur, l’object peut être déplacé à la place, de sorte que le second cas ne soit soudainement plus “copié, déplacé”, mais “déplacé, puis (potentiellement) déplacé à nouveau”.

Pour les objects de grande taille qui implémentent des constructeurs de déplacement appropriés (tels que des vecteurs, des chaînes de caractères…), le second cas est alors beaucoup plus efficace que le premier. Par conséquent, il est recommandé d’ utiliser pass by value si la fonction prend possession de l’argument et si le type d’object prend en charge le déplacement efficace .


Une note historique:

En fait, tout compilateur moderne devrait être capable de déterminer quand le passage par valeur est coûteux, et convertir implicitement l’appel pour utiliser un ref const si possible.

En théorie. En pratique, les compilateurs ne peuvent pas toujours changer cela sans casser l’interface binary de la fonction. Dans certains cas particuliers (lorsque la fonction est en ligne), la copie sera effectivement éliminée si le compilateur peut comprendre que l’object d’origine ne sera pas modifié par les actions de la fonction.

Mais en général, le compilateur ne peut pas le déterminer, et l’avènement de la sémantique de déplacement en C ++ a rendu cette optimisation beaucoup moins pertinente.


1 Par exemple dans Scott Meyers, Effective C ++ .

2 Ceci est particulièrement vrai pour les constructeurs d’objects, qui peuvent prendre des arguments et les stocker en interne pour faire partie de l’état de l’object construit.

Edit: Nouvel article de Dave Abrahams sur cpp-next:

Vous voulez de la vitesse? Passer par valeur.


Passer par valeur pour les structures où la copie est bon marché présente l’avantage supplémentaire que le compilateur peut supposer que les objects ne sont pas des alias (ne sont pas les mêmes objects). En utilisant la référence par référence, le compilateur ne peut pas toujours en tenir compte. Exemple simple:

 foo * f; void bar(foo g) { gi = 10; f->i = 2; gi += 5; } 

le compilateur peut l’optimiser dans

 gi = 15; f->i = 2; 

car il sait que f et g ne partagent pas le même emplacement. Si g était une référence (foo &), le compilateur n’aurait pas pu le supposer. puisque gi pourrait alors être aliasé par f-> i et avoir une valeur de 7. Le compilateur devrait donc récupérer la nouvelle valeur de gi en mémoire.

Pour des règles plus pratiques, voici un bon ensemble de règles trouvées dans l’article de Move Constructors (lecture hautement recommandée).

  • Si la fonction a l’intention de modifier l’argument en tant qu’effet secondaire, prenez-le par référence non-const.
  • Si la fonction ne modifie pas son argument et que l’argument est de type primitif, prenez-le par valeur.
  • Sinon, prenez-le par référence const, sauf dans les cas suivants
    • Si la fonction devait de toute façon faire une copie de la référence const, prenez-la par valeur.

“Primitive” ci-dessus signifie essentiellement de petits types de données de quelques octets de long et qui ne sont pas polymorphes (iterators, objects de fonction, etc.) ou coûteux à copier. Dans cet article, il y a une autre règle. L’idée est que parfois on veut faire une copie (au cas où l’argument ne peut pas être modifié), et parfois on ne veut pas (si l’on veut utiliser l’argument lui-même dans la fonction si l’argument était temporaire de toute façon , par exemple). Le papier explique en détail comment cela peut être fait. En C ++ 1x, cette technique peut être utilisée en mode natif avec la prise en charge du langage. Jusque-là, j’irais avec les règles ci-dessus.

Exemples: Pour rendre une chaîne en majuscule et renvoyer la version en majuscule, il faut toujours passer par valeur: On doit en prendre une copie quand même (on ne peut pas changer directement la référence const) – donc mieux le rendre aussi transparent que possible l’appelant et faire cette copie tôt pour que l’appelant puisse optimiser autant que possible – comme détaillé dans cet article:

 my::ssortingng uppercase(my::ssortingng s) { /* change s and return it */ } 

Cependant, si vous n’avez pas besoin de changer le paramètre, prenez-le par référence à const:

 bool all_uppercase(my::ssortingng const& s) { /* check to see whether any character is uppercase */ } 

Cependant, si vous voulez écrire quelque chose dans l’argument, alors passez-le par référence non-const.

 bool try_parse(T text, my::ssortingng &out) { /* try to parse, write result into out */ } 

Dépend du type. Vous ajoutez la petite surcharge de devoir faire une référence et déréférencer. Pour les types dont la taille est égale ou inférieure à celle des pointeurs utilisant le contrôleur de copie par défaut, il serait probablement plus rapide de passer par valeur.

Comme cela a été souligné, cela dépend du type. Pour les types de données intégrés, il est préférable de passer par valeur. Même de très petites structures, comme une paire d’ints, peuvent mieux fonctionner en passant par la valeur.

Voici un exemple, supposons que vous avez une valeur entière et que vous voulez la passer à une autre routine. Si cette valeur a été optimisée pour être stockée dans un registre, alors si vous voulez la passer comme référence, vous devez d’abord la stocker en mémoire, puis un pointeur sur cette mémoire placée sur la stack pour effectuer l’appel. Si elle était transmise par valeur, tout ce qui est requirejs est le registre inséré dans la stack. (Les détails sont un peu plus compliqués que ceux donnés par différents systèmes d’appel et processeurs).

Si vous faites de la programmation de modèles, vous êtes généralement obligé de toujours passer par const ref puisque vous ne connaissez pas les types passés en passant. Passer des pénalités pour avoir transmis quelque chose de mauvais en valeur est bien pire que de passer un type intégré par const ref.

On dirait que vous avez eu votre réponse. Passer par valeur est cher, mais vous donne une copie avec laquelle travailler si vous en avez besoin.

En règle générale, il est préférable de passer par la référence const. Mais si vous devez modifier votre argument de fonction localement, vous devriez mieux utiliser le passage par valeur. Pour certains types de base, les performances sont généralement les mêmes pour le passage par valeur et par référence. En fait, la référence représentée en interne par un pointeur, c’est pourquoi vous pouvez vous attendre, par exemple, à ce que le passage du pointeur soit le même en termes de performances, ou même le passage par la valeur peut être plus rapide en raison d’un déréférencement inutile.

C’est ce que je travaille normalement lors de la conception de l’interface d’une fonction non-modèle:

  1. Passez par valeur si la fonction ne veut pas modifier le paramètre et que la valeur est bon marché à copier (int, double, float, char, bool, etc …) Notez que std :: ssortingng, std :: vector et le rest des conteneurs dans la bibliothèque standard ne sont pas)

  2. Passez par const pointeur si la valeur est chère à copier et la fonction ne veut pas modifier la valeur pointée et NULL est une valeur gérée par la fonction.

  3. Passez par un pointeur non-const si la valeur est chère à copier et la fonction souhaite modifier la valeur pointée et NULL est une valeur gérée par la fonction.

  4. Passer par la référence const lorsque la valeur est coûteuse à copier et que la fonction ne veut pas modifier la valeur référencée et que NULL ne serait pas une valeur valide si un pointeur était utilisé à la place.

  5. Passez par une référence non-const lorsque la valeur est chère à copier et que la fonction souhaite modifier la valeur référencée et que NULL ne serait pas une valeur valide si un pointeur était utilisé à la place.

En règle générale, valeur pour les types hors classe et référence const pour les classes. Si une classe est vraiment petite, il vaut probablement mieux passer par valeur, mais la différence est minime. Ce que vous voulez vraiment éviter, c’est de passer une classe gigantesque par valeur et de la faire dupliquer – cela fera une énorme différence si vous passez, disons, un std :: vector avec quelques éléments.

Passez par valeur pour les petits types.

Pass by const des références pour les gros types (la définition de big peut varier entre les machines) MAIS, en C ++ 11, passez par valeur si vous allez consumr les données, car vous pouvez exploiter la sémantique de mouvement. Par exemple:

 class Person { public: Person(std::ssortingng name) : name_(std::move(name)) {} private: std::ssortingng name_; }; 

Maintenant, le code d’appel ferait:

 Person p(std::ssortingng("Albert")); 

Et un seul object serait créé et déplacé directement dans le nom du name_ dans la classe Person . Si vous passez par référence const, une copie devra être faite pour le name_ .

Différence simple: – En fonction, nous avons un paramètre d’entrée et de sortie, donc si votre paramètre d’entrée et de sortie est le même, utilisez call by reference else si les parameters d’entrée et de sortie sont différents.

exemple: void amount(int account , int deposit , int total )

paramètre d’entrée: compte, paramètre de sortie de repository: total

l’entrée et la sortie est une utilisation différente d’appel par vaule

  1. void amount(int total , int deposit )

total de la production totale des repositorys en entrée