71 votes

int vs const int &

J'ai remarqué que j'ai l'habitude d'utiliser références constantes au retour des valeurs ou des arguments. Je pense que la raison est qu'il fonctionne presque le même que l'utilisation non référence dans le code. Mais ça prend plus de place et les déclarations de fonction de plus longue durée. Je suis OK avec ce code mais je pense que certaines personnes de mon trouver un mauvais style de programmation.

Qu'en pensez-vous? Vaut-il écrit const int& plus int? Je pense que c'est optimisé par le compilateur de toute façon, alors peut-être je suis juste de perdre mon temps à coder ça, une?

164voto

6502 Points 42700

En C++, il est très courant de ce que je considère comme un anti-modèle qui utilise const T& comme une façon intelligente de dire simplement" T lorsque vous traitez avec des paramètres. Toutefois, une valeur et une référence (peu importe si const ou non) sont deux choses complètement différentes et toujours et aveuglément à l'aide de références à la place des valeurs peut conduire à des bogues subtils.

La raison en est que lorsque vous traitez avec des références vous devez tenir compte de deux questions qui ne sont pas présents avec des valeurs: la durée de vie et d'aliasing.

Juste comme exemple d'un endroit où cet anti-modèle est appliqué est le standard de la bibliothèque elle-même, où l' std::vector<T>::push_back accepte comme paramètre une const T& au lieu d'une valeur, ce qui peut mordre le dos par exemple dans le code comme:

std::vector<T> v;
...
if (v.size())
    v.push_back(v[0]); // Add first element also as last element

Ce code est une bombe à retardement, car std::vector::push_back veut une référence const mais faire la push_back peut exiger une réaffectation et si cela se produit signifie que, après la réaffectation de référence reçu ne serait plus valide (durée de vie ) et vous entrez dans le Comportement Indéfini royaume.

Aliasing questions sont également une source de problèmes subtils si const références sont utilisées à la place des valeurs. J'ai été mordu par exemple par le code de ce genre:

struct P2d
{ 
    double x, y;
    P2d(double x, double y) : x(x), y(y) {}
    P2d& operator+=(const P2d& p) { x+=p.x; y+=p.y; return *this; }
    P2d& operator-=(const P2d& p) { x-=p.x; y-=p.y; return *this; }
};

struct Rect
{
    P2d tl, br;
    Rect(const P2d& tl, const P2d& br) : tl(tl), bt(br) {}
    Rect& operator+=(const P2d& p) { tl+=p; br+=p; return *this; }
    Rect& operator-=(const P2d& p) { tl-=p; br-=p; return *this; }
};

Le code semble à première vue assez fort, P2d est une bidimensionnelle point, Rect est un rectangle et en ajoutant ou en supprimant un point signifie transformer le rectangle.

Si, toutefois, pour traduire le rectangle de retour à l'origine que vous écrivez myrect -= myrect.tl; le code ne fonctionnera pas parce que la traduction de l'opérateur a été défini en acceptant une référence qui est, dans ce cas, le référencement d'un membre de la même instance.

Cela signifie que, après la mise à jour de la topleft avec tl -= p; le topleft sera (0, 0) comme il se doit, mais aussi p va devenir dans le même temps, (0, 0) car p est juste une référence vers le haut-membre de gauche et donc la mise à jour de coin en bas à droite ne fonctionne pas car il va le traduire en (0,0) donc faire à peu près rien.

Ne soyez pas dupé en pensant que const référence, c'est comme une valeur à cause de la parole const. Ce mot n'existe que pour vous donner des erreurs de compilation si vous essayez de modifier l'objet référencé à l'aide de cette référence, mais cela ne signifie pas que l'objet référencé est constante. Plus précisément l'objet référencé par un const ref peut changer (par exemple à cause de l'aliasing), et peuvent même sortir de l'existence pendant que vous l'utilisez (durée de vie de l'émission).

En const T& le mot const exprime une propriété de la référence, et non pas de l' objet référencé: c'est la propriété qui rend impossible de l'utiliser pour modifier l'objet. Probablement readonly aurait été un meilleur nom que const a de l'OMI, l'effet psychologique de pousser l'idée que l'objet va être constante pendant que vous utilisez la référence.

Vous pouvez bien sûr obtenir impressionnant la vitesse à l'aide de références au lieu de copier les valeurs, en particulier pour les grandes classes. Mais vous devriez toujours penser à l'aliasing et la durée de vie des problèmes lors de l'utilisation de références, car sous le capot, ils sont juste des pointeurs vers d'autres données. Pour les "indigènes" des types de données (entiers, des doubles, des pointeurs) références sont cependant, en réalité, va être plus lent que les valeurs et il n'y a rien à gagner à les utiliser à la place des valeurs.

Aussi const référence signifiera toujours des problèmes pour l'optimiseur que le compilateur est forcé d'être paranoïaque et chaque fois qu'un inconnu code est exécuté, il doit l'assumer que tous les objets référencés peuvent avoir une valeur différente (const pour une référence signifie absolument RIEN pour l'optimiseur; ce mot est-il que pour aider les programmeurs à - personnellement, je suis pas si sûr, c'est d'une grande aide, mais c'est une autre histoire).

16voto

Peter Alexander Points 31990

Comme dit Oli, de retour d'un const T& plutôt T sont des choses complètement différentes, et peuvent se briser dans certaines situations (comme dans son exemple).

En prenant const T& par opposition à la plaine de T comme un argument est moins susceptible de casser des choses, mais encore plusieurs différences importantes.

  • En prenant T au lieu de const T& nécessite qu' T copie est constructible.
  • En prenant T appeler le constructeur de copie, ce qui peut être coûteux (et aussi le destructeur sur la sortie de la fonction).
  • En prenant T vous permet de modifier le paramètre comme une variable locale (peut être plus rapide que la copie manuelle).
  • En prenant const T& peut être plus lente due à un défaut d'alignement temporaires et le coût d'indirection.

8voto

Oli Charlesworth Points 148744

int & et int ne sont pas interchangeables! En particulier, si vous renvoyer une référence à une pile locale variable, le comportement est indéfini, par exemple:

int &func()
{
    int x = 42;
    return x;
}

Vous pouvez renvoyer une référence à quelque chose qui ne sera pas détruite à la fin de la fonction (par exemple, un statique, ou un membre de la classe). C'est donc valide:

int &func()
{
    static int x = 42;
    return x;
}

et le monde extérieur, a le même effet que le retour de l' int directement (à l'exception que vous pouvez maintenant le modifier, c'est pourquoi vous voyez const int & beaucoup).

L'avantage de la référence est qu'aucune copie n'est nécessaire, ce qui est important si vous faites affaire avec de grands objets de la classe. Cependant, dans de nombreux cas, le compilateur peut optimiser ce que l'écart; voir par ex. http://en.wikipedia.org/wiki/Return_value_optimization.

8voto

Philipp Points 21479

Si l'appelé et de l'appelant sont définies à séparer les unités de compilation, alors le compilateur ne peut pas optimiser loin la référence. Par exemple, j'ai compilé le code suivant:

#include <ctime>
#include <iostream>

int test1(int i);
int test2(const int& i);

int main() {
  int i = std::time(0);
  int j = test1(i);
  int k = test2(i);
  std::cout << j + k << std::endl;
}

avec G++ sous Linux 64 bits à l'optimisation de niveau 3. Le premier appel a besoin d'aucun accès à la mémoire principale:

call    time
movl    %eax, %edi     #1
movl    %eax, 12(%rsp) #2
call    _Z5test1i
leaq    12(%rsp), %rdi #3
movl    %eax, %ebx
call    _Z5test2RKi

Ligne n ° 1 utilise directement la valeur de retour dans eax comme argument pour test1 en edi. La ligne #2 et #3 poussez le résultat dans la mémoire principale et de la place de l'adresse dans le premier argument, car l'argument est déclaré en tant que référence de type int, et donc il doit être possible par exemple de prendre son adresse. Si quelque chose peut être entièrement calculé en utilisant les registres ou les besoins d'accès à la mémoire principale peut faire une grande différence ces jours-ci. Donc, en plus d'être plus à taper, const int& peut également être plus lent. La règle de base est, transmettre toutes les données qui est plus grande que celle de la taille de mot en valeur, et tout le reste par référence const. Également passer basé sur un modèle d'arguments par référence const; car le compilateur a accès à la définition du modèle, il peut toujours optimiser la référence à l'écart.

3voto

Au lieu de "penser" c'est optimisé par le compilateur, pourquoi ne pas vous obtenez l'assembleur d'inscription et de trouver pour vous?

de la camelote.c++:

int my_int()
{
    static int v = 5;
    return v;
}

const int& my_int_ref()
{
    static int v = 5;
    return v;
}

Généré assembleur de sortie (élidée):

_Z6my_intv:
.LFB0:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    movl    $5, %eax
    ret
    .cfi_endproc

...

_Z10my_int_refv:
.LFB1:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    movl    $_ZZ10my_int_refvE1v, %eax
    ret

L' movl des instructions dans les deux sont très différents. Le premier se déplace 5 en EAX (qui se trouve être le registre traditionnellement utilisé pour les valeurs de retour en x86 code C) et le second se déplace l'adresse d'une variable (détails gommés pour plus de clarté) en EAX. Cela signifie que la fonction d'appel dans le premier cas peut juste utiliser directement registre des opérations sans frapper la mémoire à utiliser la réponse tandis que dans le second, il a frappé la mémoire à travers le pointeur retourné.

Donc, il semble que ce n'est pas optimisé à l'écart.

C'est plus que les autres réponses que vous avez été donné ici d'expliquer pourquoi T et const T& ne sont pas interchangeables.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X