Passage par référence

Nous avons vu précédemment que la fonction echange n’échangeait rien du tout. La solution « ancienne » (langage C) à ce problème consiste à utiliser les pointeurs ; la fonction connaît alors l’adresse des arguments, et peut donc les modifier en déréférençant les pointeurs :

void echange(int *a, int *b)
{
     int c = *a;
     *a = *b; *b = c;
}

L’inconvénient de cette méthode est qu’il faut préciser explicitement l’opérateur d’adressage dans un appel à la fonction :

int i = 4, j = 6;
echange (&i, &j);  // à présent i == 6, j == 4

Pour résoudre ce problème, on peut en C++ utiliser les références ; il suffit d’indiquer pour arguments des références int& :

void echange(int &a, int &b)
{
     int c = a;
     a = b; b = c;
}

On peut alors écrire :

int i = 4, j = 6;
echange (i, j);  // à présent i == 6, j == 4

ce qui est nettement plus naturel. Que se passe-t-il alors ? Au lieu de créer une case mémoire spéciale pour recopier les valeurs des paramètres, ce sont les adresses de ceux-ci qui sont en réalité passées à la fonction, sous la forme d’une référence (qui est donc ici une sorte de « pointeur implicite » ). Celle-ci peut donc modifier les valeurs sur place.

Que se passe-t-il si l’on écrit :

int i = 4, j = 6;
echange (i+j, j) // incorrect;

Dans ce cas, i+j n’est pas une variable, on ne peut donc donner son adresse. D’ailleurs, avec la méthode des pointeurs, comme &(i+j) n’a pas de sens, une erreur serait provoquée.

Ici, il n’y a pas réellement d’erreur. En effet, nous avons vu qu’il est possible d’initialiser une référence avec une constante ; dans ce cas, une variable temporaire est créée, initialisée à la valeur de i+j, soit 10, et c’est une référence à cette variable que désigne a. On voit que dans ce cas, le comportement est pratiquement identique à celui d’un argument normal par valeur.

Le résultat obtenu est alors de recopier i+j dans j. L’autre recopie s’effectue sur une variable temporaire, donc est sans effet. En particulier, i n’est pas modifié.

A fortiori, si l’on écrit :

echange(1, 2);

l’appel est dépourvu d’effet.

Ce comportement peut paraître curieux et inutile, mais il est parfois bien pratique. Imaginons ainsi que nous ayons écrit une fonction, version étendue de echange, qui permute circulairement quatre arguments (c’est-à-dire copie le premier dans le second, le second dans le troisième, etc., et le quatrième dans le premier). On utilisera en général cette fonction avec quatre variables, mais parfois on peut écrire dans le programme :

int i = 1, j = 2, k = 3;
permute (0, i, j, k);

qui revient à copier 0 dans i, i dans j, et j dans k. On réutilise ainsi partiellement les capacités de la fonction, grâce à cette possibilité de créer des références constantes (ce qui est par contre tout à fait interdit avec les pointeurs).

Dans certains cas, il est préférable de passer une référence sur un objet, plutôt que l’objet tout entier, non parce que la fonction souhaite le modifier, mais simplement parce qu’il s’agit d’un objet de grande taille (en mémoire). Pour l’instant, nous n’avons pas vu d’objet plus grand que long double (10 octets), mis à part les tableaux qui de toute façon ne sont jamais passés par valeur. Cependant, on peut en créer avec les structures et des classes (chapitre suivant). Dans ce cas, il se peut que l’appel d’une fonction du genre :

void f(grandtype g)

fasse déborder la pile si la taille du type grandtype est importante ; et de toute façon, cela ralentit le programme, puisqu’il recopie sur la pile le paramètre g. Si la fonction ne modifie pas son argument, cette recopie est tout à fait inutile. Dans ce cas, on a intérêt à passer l’argument par référence ; pour bien montrer que l’on ne fait cela que dans un but d’optimisation, et non pour modifier l’argument, on donnera une référence constante (voir sous-paragraphe suivant pour une explication sur les arguments constants), comme ceci :

void f(const grandtype& g);

Cette méthode peut être envisagée même pour le type double (bien qu’elle soit alors un peu plus lente) si l’on souhaite ne pas engorger la pile (fonctions récursives par exemple).

Précédent Précédent Sommaire Sommaire Suivant Suivant