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 référence constante devrait permettre de meilleures performances dans le programme car vous ne faites pas de copie de la variable.
c++
variables
pass-by-reference
constants
pass-by-value
Matt Pascoe
la source
la source
Réponses:
Il a utilisé la meilleure pratique généralement recommandée 1 à l'utilisation passer par ref const pour tous les types , à l' exception des types (builtin
char
,int
,double
, etc.), pour itérateurs et des objets de fonction (lambdas, classes dérivées destd::*_function
).Cela était particulièrement vrai avant l'existence de la sémantique des mouvements . La raison est simple: si vous passez par valeur, une copie de l'objet doit être faite et, sauf pour les très petits objets, c'est toujours plus cher que de passer une référence.
Avec C ++ 11, nous avons acquis une sémantique de mouvement . En un mot, la sémantique de mouvement permet que, dans certains cas, un objet puisse être passé «par valeur» sans le copier. C'est notamment le cas lorsque l'objet que vous passez est une valeur r .
En soi, déplacer un objet est toujours au moins aussi cher que passer par référence. Cependant, dans de nombreux cas, une fonction copiera de toute façon un objet en interne - c'est-à-dire qu'elle s'appropriera l'argument. 2
Dans ces situations, nous avons le compromis (simplifié) suivant:
"Passer par valeur" entraîne toujours la copie de l'objet, sauf si l'objet est une valeur r. Dans le cas d'une valeur r, l'objet peut être déplacé à la place, de sorte que le deuxième cas n'est plus soudainement «copier, puis déplacer» mais «déplacer, puis (potentiellement) se déplacer à nouveau».
Pour les gros objets qui implémentent des constructeurs de déplacement appropriés (tels que des vecteurs, des chaînes…), le deuxième cas est alors beaucoup plus efficace que le premier. Par conséquent, il est recommandé d' utiliser la fonction passer par valeur si la fonction prend possession de l'argument et si le type d'objet prend en charge un déplacement efficace .
Une note historique:
En fait, tout compilateur moderne devrait être capable de comprendre quand le passage par valeur est coûteux, et convertir implicitement l'appel pour utiliser une référence const si possible.
En théorie. En pratique, les compilateurs ne peuvent pas toujours changer cela sans casser l'interface binaire de la fonction. Dans certains cas spéciaux (lorsque la fonction est en ligne), la copie sera réellement éluée si le compilateur peut comprendre que l'objet d'origine ne sera pas modifié par les actions de la fonction.
Mais en général, le compilateur ne peut pas déterminer cela, et l'avènement de la sémantique des mouvements en C ++ a rendu cette optimisation beaucoup moins pertinente.
1 Par exemple dans Scott Meyers, Effective C ++ .
2 Cela est particulièrement vrai pour les constructeurs d'objets, qui peuvent prendre des arguments et les stocker en interne pour faire partie de l'état de l'objet construit.
la source
Éditer: Nouvel article de Dave Abrahams sur cpp-next:
Envie de vitesse? Passez par valeur.
Passer la valeur pour les structures où la copie est bon marché a l'avantage supplémentaire que le compilateur peut supposer que les objets ne sont pas alias (ne sont pas les mêmes objets). En utilisant pass-by-reference, le compilateur ne peut pas toujours supposer cela. Exemple simple:
le compilateur peut l'optimiser en
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 supposer cela. puisque gi pourrait alors être aliasé par f-> i et avoir une valeur de 7. donc le compilateur devrait récupérer à nouveau la nouvelle valeur de gi dans la mémoire.
Pour des règles plus pratiques, voici un bon ensemble de règles trouvées dans l' article Move Constructors (lecture fortement recommandée).
"Primitif" ci-dessus signifie essentiellement de petits types de données qui font quelques octets de long et qui ne sont pas polymorphes (itérateurs, objets fonction, etc ...) ou coûteux à copier. Dans ce document, 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 (au cas où l'on veut utiliser l'argument lui-même dans la fonction si l'argument était de toute façon temporaire , par exemple). Le document explique en détail comment cela peut être fait. En C ++ 1x, cette technique peut être utilisée nativement avec le support du langage. Jusque-là, j'irais avec les règles ci-dessus.
Exemples: Pour rendre une chaîne en majuscules et renvoyer la version en majuscules, il faut toujours passer par valeur: il faut quand même en prendre une copie (on ne peut pas changer directement la référence const) - il vaut donc mieux la rendre aussi transparente que possible pour l'appelant et faire cette copie tôt afin que l'appelant puisse optimiser autant que possible - comme détaillé dans cet article:
Cependant, si vous n'avez pas besoin de changer le paramètre de toute façon, prenez-le en référence à const:
Cependant, si vous le but du paramètre est d'écrire quelque chose dans l'argument, passez-le par référence non const
la source
__restrict__
(qui peut également fonctionner sur des références) plutôt que de faire trop de copies. Dommage que le standard C ++ n'ait pas adopté lerestrict
mot clé C99 .Dépend du type. Vous ajoutez la petite surcharge de devoir faire une référence et un déréférencement. Pour les types de taille égale ou inférieure aux pointeurs qui utilisent le ctor de copie par défaut, il serait probablement plus rapide de passer par valeur.
la source
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 certaines très petites structures, comme une paire d'ts, peuvent mieux fonctionner en passant par la valeur.
Voici un exemple, supposons que vous ayez une valeur entière et que vous souhaitiez la transmettre à 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, elle doit d'abord être stockée en mémoire puis un pointeur vers cette mémoire placé sur la pile pour effectuer l'appel. S'il était passé par valeur, tout ce qui est requis est le registre poussé sur la pile. (Les détails sont un peu plus compliqués que cela étant donné les différents systèmes d'appel et CPU).
Si vous faites de la programmation de modèles, vous êtes généralement obligé de toujours passer par const ref car vous ne connaissez pas les types transmis. Passer des pénalités pour passer quelque chose de mauvais par valeur est bien pire que les pénalités de passer un type intégré par const ref.
la source
Voici ce que je travaille normalement lors de la conception de l'interface d'une fonction non-modèle:
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 :: string, std :: vector et le reste des conteneurs de la bibliothèque standard ne sont PAS)
Passez le pointeur const si la valeur est coûteuse à copier et que la fonction ne souhaite pas modifier la valeur pointée et que NULL est une valeur que la fonction gère.
Passez par un pointeur non const si la valeur est coûteuse à copier et que la fonction souhaite modifier la valeur pointée et que NULL est une valeur que la fonction gère.
Passez par référence const lorsque la valeur est coûteuse à copier et que la fonction ne souhaite pas modifier la valeur référencée et NULL ne serait pas une valeur valide si un pointeur était utilisé à la place.
Passez par une référence non constante lorsque la valeur est coûteuse à copier et que la fonction souhaite modifier la valeur à laquelle il est fait référence et que NULL ne serait pas une valeur valide si un pointeur était utilisé à la place.
la source
std::optional
à l'image et vous n'avez plus besoin de pointeurs.On dirait que vous avez obtenu votre réponse. Passer par la valeur coûte cher, mais vous en donne une copie si vous en avez besoin.
la source
En règle générale, passer par référence const est préférable. 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. Référencer réellement représenté en interne par un pointeur, c'est pourquoi vous pouvez vous attendre par exemple à ce que pour le pointeur les deux passages soient les mêmes en termes de performances, ou même le passage par valeur peut être plus rapide en raison de la déférence inutile.
la source
En règle générale, valeur pour les types non-classe et référence const pour les classes. Si une classe est vraiment petite, il vaut probablement mieux passer par la 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 dupliquer - cela fera une énorme différence si vous passez, disons, un std :: vector avec pas mal d'éléments.
la source
std::vector
réellement alloue ses éléments sur le tas et l'objet vectoriel lui-même ne se développe jamais. Oh, attendez. Si l'opération entraîne une copie du vecteur, elle ira en fait dupliquer tous les éléments. Ce serait mauvais.sizeof(std::vector<int>)
est constant, mais le transmettre par valeur copiera toujours le contenu en l'absence de toute intelligence du compilateur.Passez par valeur pour les petits types.
Passez les références const pour les gros types (la définition de big peut varier selon les machines) MAIS, en C ++ 11, passez par valeur si vous allez consommer les données, car vous pouvez exploiter la sémantique des mouvements. Par exemple:
Maintenant, le code d'appel ferait:
Et un seul objet serait créé et déplacé directement dans le membre
name_
de la classePerson
. Si vous passez par référence const, une copie devra être faite pour le mettre dedansname_
.la source
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 appel par référence sinon si les paramètres d'entrée et de sortie sont différents, il vaut mieux utiliser l'appel par valeur.
exemple
void amount(int account , int deposit , int total )
paramètre d'entrée: compte, paramètre de sortie de dépôt: total
entrée et sortie est un appel d'utilisation différent par vaule
void amount(int total , int deposit )
total d'entrée dépôt total de sortie
la source