Question

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

Je me demande quelle est la meilleure pratique. Je me rends compte que passer par référence constante devrait permettre une meilleure performance du programme car vous ne faites pas de copie de la variable.

Était-ce utile?

La solution

Auparavant, la meilleure pratique recommandée 1 consistait à utiliser la référence constante pour tous les types , à l'exception des types intégrés ( char , int , double , etc.), pour les itérateurs et pour les objets fonction (lambdas, classes dérivées de std :: * _ function ).

Cela était particulièrement vrai avant l'existence de la sémantique de déplacement . La raison en est simple: si vous transmettez valeur par valeur, une copie de l’objet doit être créée et, à l’exception des objets très petits, elle est toujours plus chère que de passer une référence.

Avec C ++ 11, nous avons acquis la sémantique de déplacement . En résumé, la sémantique de déplacement permet, dans certains cas, de passer un objet «par valeur» sans le copier. C'est notamment le cas lorsque l'objet que vous transmettez est un rvalue . / p>

En soi, le déplacement d'un objet est toujours aussi coûteux que le passage par référence. Cependant, dans de nombreux cas, une fonction copie de toute façon en interne un objet - c’est-à-dire qu’elle prendra la propriété de l’argument. 2

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

  1. Nous pouvons transmettre l'objet par référence, puis copier en interne.
  2. Nous pouvons transmettre l'objet par valeur.

"Pass par valeur" provoque toujours la copie de l'objet, sauf s'il est une valeur rvalue. Dans le cas d'une valeur rvalue, l'objet peut être déplacé à la place, de sorte que le second cas ne soit plus du tout "copier, puis déplacer" mais "déplacer, puis (éventuellement) déplacer à nouveau".

Pour les objets volumineux qui implémentent les constructeurs de déplacement appropriés (tels que les vecteurs, les chaînes de caractères, etc.), le second cas est alors très largement plus efficace que le premier. Par conséquent, il est recommandé d'utiliser pass par valeur si la fonction devient propriétaire de l'argument et si le type d'objet prend en charge le déplacement efficace .

Une note historique:

En fait, tout compilateur moderne devrait être en mesure de déterminer si le passage par la valeur est coûteux et, implicitement, convertir l'appel pour utiliser une référence constante 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 particuliers (lorsque la fonction est en ligne), la copie sera supprimée si le compilateur peut déterminer que l’objet original 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 l’optimisation beaucoup moins pertinente.

1 E.g. dans Scott Meyers, Effective C ++ .

2 Ceci 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.

Autres conseils

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

Voulez-vous de la vitesse? Pass par valeur.

Le passage par valeur pour les structures où la copie est peu coûteuse présente l’avantage supplémentaire que le compilateur peut supposer que les objets ne sont pas des alias (ne sont pas les mêmes objets). En utilisant passe par référence, le compilateur ne peut pas assumer cela toujours. Exemple simple:

foo * f;

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

le compilateur peut l'optimiser dans

g.i = 15;
f->i = 2;

puisqu'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 g.i pourrait alors être aliasé par f-> i et devrait avoir une valeur de 7. le compilateur devrait donc extraire à nouveau la nouvelle valeur de g.i de la mémoire.

Pour des règles plus pratiques, voici un bon ensemble de règles figurant dans l'article Déplacer les constructeurs ( lecture fortement recommandée).

  • Si la fonction a l'intention de changer l'argument en tant qu'effet secondaire, prenez-le par référence non constante.
  • 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 alors quand même faire une copie de la référence const, prenez-la par valeur.

" Primitive " ci-dessus signifie essentiellement des types de données de petite taille, longs de quelques octets et non polymorphes (itérateurs, objets de 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ù on voudrait 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 de manière native avec le support de la langue. Jusque-là, j'irais avec les règles ci-dessus.

Exemples: Pour faire une chaîne en majuscule et renvoyer la version en majuscule, il faut toujours passer par valeur: il faut en prendre une copie (on ne peut pas changer directement la référence const) - il vaut donc mieux la rendre transparente. le plus tôt possible à l'appelant et effectuez cette copie à l'avance afin que l'appelant puisse optimiser autant que possible - comme indiqué dans cet article:

my::string uppercase(my::string 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::string const& s) { 
    /* check to see whether any character is uppercase */
}

Cependant, si le paramètre a pour but d'écrire quelque chose dans l'argument, transmettez-le par référence non-const

bool try_parse(T text, my::string &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érencement. Pour les types de taille égale ou inférieure aux pointeurs qui utilisent le copieur 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’entités, peuvent mieux fonctionner en passant par valeur.

Voici un exemple, supposons que vous avez une valeur entière et que vous voulez la transmettre à une autre routine. Si cette valeur a été optimisée pour être stockée dans un registre, si vous souhaitez la transmettre comme référence, elle doit d’abord être stockée en mémoire, puis un pointeur sur cette mémoire placée sur la pile pour effectuer l’appel. S'il était passé par valeur, il suffit que le registre soit placé dans la pile. (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 par modèle, vous êtes généralement obligé de toujours passer par référence constante car vous ne connaissez pas les types transférés. Le fait de passer des pénalités pour avoir passé quelque chose de mauvais par valeur est bien pire que de passer un construit -in type par référence constante

On dirait que vous avez eu votre réponse. Passer par valeur coûte cher, mais vous en donne une copie si vous en avez besoin.

C’est avec quoi je travaille normalement lors de la conception de l’interface d’une fonction non-template:

  1. Passer par valeur si la fonction ne veut pas modifier le paramètre et le la valeur est économique à 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)

  2. Passer par le pointeur const si la valeur est chère à copier et que la fonction le fait veut pas modifier la valeur pointée vers et NULL est une valeur que la fonction gère.

  3. Passer par le pointeur non-const si la valeur est chère à copier et la fonction veut modifier la valeur indiquée et NULL est une valeur que la fonction gère.

  4. Passer par référence const lorsque la valeur coûte cher à copier et que la fonction ne veut pas modifier la valeur à laquelle il est fait référence, et NULL ne serait pas une valeur valide si un pointeur était utilisé à la place.

  5. Passer par référence non-const lorsque la valeur est chère à copier et que la fonction veut modifier la valeur à laquelle il est fait référence. NULL ne serait pas une valeur valide si un pointeur était utilisé à la place.

En règle générale, mieux vaut passer par référence de const. Mais si vous avez besoin de 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, référence référencée en interne représentée par un pointeur, c’est pourquoi on peut s’attendre, par exemple, à ce que les points de passage soient identiques en termes de performances, ou même que le passage par valeur soit plus rapide en raison d’un déréférencement inutile.

En règle générale, valeur pour les types non-classes 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 par une classe gigantesque par valeur et de tout dupliquer - cela fera une énorme différence si vous passez, par exemple, un std :: vector avec pas mal d’éléments.

Pass par valeur pour les petits types.

Passer les références const pour les gros types (la définition de gros 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 du déplacement. Par exemple:

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

Maintenant, le code d'appel ferait:

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

Et un seul objet serait créé et déplacé directement dans le membre nom _ de la classe Personne . Si vous passez par référence const, il faudra en faire une copie pour la mettre dans nom _ .

Différence simple: - Dans les fonctions, nous avons les paramètres d’entrée et de sortie. Ainsi, si vos paramètres d’entrée et de sortie sont identiques, utilisez appel par référence sinon, si les paramètres d’entrée et de sortie sont différents, il vaut mieux utiliser appel par valeur.

exemple montant void (compte int, dépôt int, total int)

paramètre d'entrée: compte, dépôt paramètre de sortie: total

input and out est différent, appel appel par vaule

  1. montant nul (int total, dépôt int)

dépôt total total de sortie

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top