Aller au contenu
Topos Uranos

Résumé

Cette leçon étudie par quels chemins les données entrent dans une fonction et ses résultats en sortent. Par défaut, C++ passe les arguments par valeur : le paramètre est une copie, et rien de ce que la fonction lui fait n’atteint l’original. Une référence, en revanche, est un second nom pour une case qui existe déjà, et elle permet à la fonction de modifier la variable de l’appelant ; la référence constante permet de lire une donnée volumineuse sans la copier et sans pouvoir l’altérer. Pour renvoyer plusieurs résultats, la bibliothèque offre std::pair et std::tuple, que l’on reçoit avec des liaisons structurées. Suivent la portée d’un nom (où il est visible) et la durée de stockage d’une variable (quand elle existe), avec le masquage d’un nom et les variables locales static, et la leçon se clôt sur la référence qui survit à sa variable. Le concept qui unifie le tout est le flux de données entre les parties d’un programme : copier ou partager, et la visibilité des noms comme mécanisme d’isolement.

Objectifs d’apprentissage

  1. Prédire l’effet d’une fonction sur ses arguments selon qu’ils sont passés par valeur ou par référence.
  2. Choisir le mode de passage de chaque paramètre (valeur pour les petits types, référence constante pour les données volumineuses, référence pour ce que l’on modifie) et justifier ce choix.
  3. Renvoyer plusieurs résultats avec std::pair ou std::tuple et les recevoir avec des liaisons structurées.
  4. Déterminer la portée et la durée de stockage de chaque variable d’un programme, y compris les variables locales static et celles qu’une autre masque.
  5. Reconnaître la référence à une variable locale ou à une valeur temporaire qui n’existe plus, avec l’avertissement du compilateur qui l’annonce, et la corriger.

Passage par valeur

Le paramètre est une copie

La leçon Fonctions a montré que chaque appel crée un cadre sur la pile, avec les paramètres et les variables locales de la fonction, et que le cadre disparaît au retour. Il reste à préciser quelle relation un paramètre entretient avec l’argument qui l’initialise. La réponse par défaut de C++ est la plus simple : le paramètre est une nouvelle variable locale, initialisée avec une copie de la valeur de l’argument. S’il en est ainsi, une fonction qui modifie ses paramètres modifie ses copies, et les variables de l’appelant ne changent pas. Le programme suivant le met à l’épreuve avec une fonction qui tente d’échanger deux entiers.

Programme en C++
#include <print>​// Tente d’échanger a et b ; n’échange que leurs copies.void swapValues(int a, int b){    const int saved{a};    a = b;    b = saved;    std::println("Dedans : a = {}, b = {}", a, b);}​int main(){    int x{1};    int y{2};    swapValues(x, y);    std::println("Après :  x = {}, y = {}", x, y);}
Sortie
Dedans : a = 2, b = 1Après :  x = 1, y = 2

Dans la fonction, l’échange se fait correctement ; après l’appel, x et y conservent leurs valeurs. Le compilateur n’avertit pas, car il n’y a rien à signaler : le code échange deux variables locales qui disparaissent à la fin. La figure suit les cadres de la pile pendant l’appel.

ExempleDeux cadres, quatre cases

Le cadre de main contient x et y ; l’appel crée au-dessus le cadre de swapValues, avec ses propres cases.

Démonstration

  1. Avant l’appel, le cadre de main a deux cases : x avec 1 et y avec 2.

  2. L’appel crée le cadre de swapValues. Ses paramètres a et b sont des cases nouvelles, et on y copie les valeurs des arguments : 1 et 2. À partir de ce moment, il ne reste aucun lien entre a et x, si ce n’est qu’ils ont commencé avec la même valeur.

  3. La variable locale saved est créée dans le même cadre et garde une copie de a.

  4. Les deux affectations échangent a et b. Les cases qui changent sont toutes dans le cadre du haut ; celles de main ne sont pas touchées.

  5. Au retour, le cadre de swapValues disparaît tout entier, avec l’échange qu’il contenait. x et y valent toujours 1 et 2.

Ce que la copie protège

Le passage par valeur n’est pas un défaut, mais la règle qui rend les fonctions raisonnables. Premièrement, l’argument peut être n’importe quelle expression, pas seulement une variable : swapValues(x + 1, 7) est valide, car le paramètre s’initialise avec une valeur, d’où qu’elle vienne. Deuxièmement, l’appelant sait par la déclaration, sans lire le corps, que ses variables ne changeront pas. Troisièmement, dans la fonction, le paramètre est une variable locale de plus, qui peut servir de variable de travail sans conséquences à l’extérieur.

Programme en C++
#include <print>​// Nombre de chiffres décimaux de n.// Précondition : n >= 0.// Postcondition : renvoie le nombre de chiffres de n (1 pour 0).int digitCount(int n){    int count{1};    while (n >= 10) {        n = n / 10;        ++count;    }    return count;}​int main(){    const int population{48'210};    std::println("{} a {} chiffres", population, digitCount(population));}
Sortie
48210 a 5 chiffres

La fonction détruit son paramètre en le divisant par 10 jusqu’à ce qu’il n’ait plus qu’un chiffre ; comme c’est une copie, population reste intacte, et elle peut être const. Il en découle une règle de lecture valable pour tout le cours : si un paramètre ne porte pas le signe & que l’on étudie dans la section suivante, la fonction ne peut pas modifier la variable de l’appelant, quoi qu’elle fasse de ce paramètre.