12) En C++, parmi nos expressions favorites (au moins ici), il y a référence constante et personnellement, je précise toujours référence modifiante
Le langage définit const comme s'appliquant au type situé à sa gauche, sauf s'il est écrit complètement à gauche de la déclaration.
Ainsi, const Matrice & Mat1 signifie la même chose que Matrice const & Mat1.
Comme de plus, les blancs ne comptent pas si le compilateur n'a pas d'autre choix que de séparer les mots, on peut aussi écrire Matrice const& Mat1.
Et là, magiquement, le mot référence constante est écrit: const&.
Aussi, j'aurai personnellement déclaré la fonction autrement:
Matrice multiplication(Matrice const& Mat1, Matrice const& Mat2);
L'intérêt est assez mineur dans ce cas, mais imagine plutot les deux écritures suivantes de la même fonction:
1 2
| std::vector<std::pair<int, int>> merge(const std::vector<std::pair<int, int>>&, const std::vector<std::pair<int, int>> &);
std::vector<std::pair<int, int>> merge(std::vector<std::pair<int, int>> const&, std::vector<std::pair<int, int>> const&); |
Laquelle des deux versions te semble plus lisible?
Il faut aussi comprendre la raison de la référence constante.
Un objet peut être assez gros, donc long à copier, voire même ne pas être copiable.
Tout argument d'une fonction est donné par copie.
En fait, par initialisation d'une variable.
Et une référence est triviale à initialiser, exactement comme un pointeur. Ca ne coute pas plus cher que d'initialiser un int avec une valeur donnée.
Ainsi, définir un paramètre de fonction comme référence permet d'éviter le coût du constructeur de copie.
Mais vient un second soucis, on ne veut pas forcément modifier une variable, et surtout, on veut pouvoir transmettre une constante à une fonction.
par exemple, une fonction qui prend un complexe et un vecteur et retourne le complexe "au bout" du vecteur serait:
complexe operator+(complexe c, vecteur v) {return c += v;}
C'est même la manière canonique d'écrire un tel opérateur (aux références près).
Sauf que la, il y a deux copie de complexe (une en entrée, et une en sortie), et une du vecteur.
Si on prend des références modifiantes, on obtient
complexe operator+(complexe & c, vecteur & v) {return c += v;}
Et là, on a certes une copie de complexe en sortie, mais surtout, on modifie c, le complexe en entrée, ce qu'on ne voulait pas.
Du coup, il faut des références constantes.
complexe operator+(complexe const & c, vecteur const& v) {return complexe(c) += v;}
Ainsi, on n'a plus de copie de vecteur du tout, et plus qu'une seule copie de complexe.
La seconde copie est en générale supprimée par les compilateurs, car ils créent la copie locale ([c]complexe(c)) directement dans la variable qui recevra la valeur de retour de la fonction. (RVO: return value optimisation)
Et surtout, tu ne modifies pas les valeurs transmises en arguments
Les références modifiantes référant toujours sur une variable, l'instruction complexe c = complexe(0,0) + vector(1, 4); n'est pas valide avec la seconde version, car complexe(0,0) et vector(1,4) ne sont pas des variables, mais des valeurs temporaires.
Avec des références constantes, c'est possible.
Tu pourrais déclarer la fonction ainsi:
complexe operator+(complexe const c, vecteur const v) {return complexe(c) += v;}
.
Mais comme les arguments sont constant, il faut créer une copie locale (et non constante) de c pour pouvoir appeler +=.
Et du coup, tu paies trois copie de complexe: une en appelant la fonction, une pour créer la valeur temporaire, et une en sortie (sauf RVO).
En fait, ce n'est pas const qui nous importe vraiment dans l'argument, mais const&.
Partager