DEV Community

Lena
Lena

Posted on

Les paramètres en C++

Mise en contexte

Le C++ est un langage puissant, multi-paradigme avec des fonctionnalités de haut niveau mais permettant aussi de faire du code de bas niveau et de l’encapsuler. Mais cela peut le rendre difficile à appréhender, sans compter des syntaxes qui peuvent paraître plus ou moins barbares aux yeux des non-initiés. Il est temps de corriger ça.

Dans cet article, nous allons parler de comment choisir la meilleure manière de passer des arguments à une fonction, et la meilleure manière de renvoyer la valeur de retour. Cela peut paraitre évident, mais en C++, il y a quelques particularités qui peuvent rendre la chose moins triviale qu’il n’y parait.

Prenons une classe toute simple contenant une std::string et un int, avec des méthodes pour récupérer et modifier ces attributs:

class an_amazing_class
{
    public:
        int     get_int() const;
        void    set_int(int new_int);

        const std::string&  get_str() const;
        void                set_str(const std::string& str);

    private:
        int        _int;
        std::string _str;
};
Enter fullscreen mode Exit fullscreen mode

On peut voir que les prototypes des deux méthodes concernant la std::string sont assez différentes de celles concernant l’int. Pour l’entier on utilise le type seul comme type de l’argument et type de retour, mais pour la chaine de caractère on rajoute un « const » devant et un « & » après.

Pourquoi ? Qu’est ce que cela veut dire ? Vous commencez peut-être à vous dire que le C++ est compliqué et que vous feriez mieux d’utiliser un autre langage, mais ne vous inquiétez pas : nous allons éclaircir tout ça dans la suite de l’article.

La copie

Commençons par le cas qui semble le plus simple : l’entier, on a juste écrit son type sans fioritures autour. Cela veut donc dire que la valeur utilisée en argument/renvoyée comme valeur de retour est une copie. Concrètement cela veut dire que le constructeur par copie est appelé, une nouvelle variable est créée avec la même valeur, pour un cas aussi simple qu’un entier, cela copie donc la valeur de notre entier dans un nouvel entier qui est envoyé/retourné par la suite.

Note : si c’est un type défini par le langage (les nombres, références, pointeurs, tableaux et les énumérations), il n’y a techniquement pas de constructeur par copie mais le comportement est le même. Source

Les références immutables

Voyons maintenant le « & ». Il signifie qu’il s’agit d’une référence, donc au lieu d’avoir une copie de la variable, on a l’adresse de la variable pour pouvoir y accéder, le mot clef « const » signifie qu’on ne peut pas la modifier. Finalement, cela envoie l’adresse de la variable et cette variable ne peut pas être modifiée.

Comparaison

Pour résumer, la copie crée une nouvelle variable en utilisant le constructeur par copie, la référence immutable utilise l’adresse de la variable pour pouvoir y accéder mais sans pouvoir la modifier.

Quand utiliser quoi ?

Si la variable a une taille plus petite ou à peu près semblable à celle d’un pointeur et qu’il est trivialement copiable (pas d’allocation de mémoire ou de traitements lourd), on préféra utiliser une copie, car la syntaxe est plus simple à lire, que c’est tout aussi rapide en temps d’exécution et qu’il n’y a pas besoin de faire attention à la portée de la variable utilisée.

Cependant, si la variable n’est pas trivialement copiable (un std::vector ou un ___std::string), si elle n’est simplement pas copiable (std::fstream_) ou bien si sa taille est supérieur à celle d’un pointeur, il vaudra mieux utiliser une référence immutable.

Note : En revanche, avec les références immutables, il faut faire attention à ne pas retourner de référence d’une variable locale, car la référence vers cette variable ne sera pas valide au-delà de la portée de la variable (généralement la fin de la fonction). Normalement, votre compilateur devrait vous prévenir si jamais vous tentez de le faire.

Exemple :

const std::string& do_not_do_this()
{
    std::string str = "do not reproduce this at home, it is dangerous";
    return str;
}
Enter fullscreen mode Exit fullscreen mode

Source

Note : Dans les boucles for parcourant des conteneurs (comme un std::vector), les mêmes règles s’appliquent.

std::vector<std::string> strings;

for (const std::string& str: strings)
{
    // do stuff
}

std::vector<int> integers;
for (int i: integers)
{
    // do stuff
}
Enter fullscreen mode Exit fullscreen mode

Quelques cas particuliers

Multithread

Si jamais votre variable est utilisée dans plusieurs threads et que l’un des threads est susceptible de modifier la variable, utiliser une copie peut être une bonne idée au lieu de devoir utiliser un mutex à chaque fois que l’on veut accéder à la variable.

Les pointeurs intelligents (smart pointer)

Si vous voulez utiliser une variable contenue dans un pointeur intelligent (std::shared_ptr par exemple), il vaut mieux utiliser une référence ou un pointeur immutable vers la variable pointée.
Source

Les templates

En général, pour les templates, il vaut mieux utiliser une référence immutable à moins d’être sur que le type utilisé sera trivialement copiable et assez petit.

Récapitulatif des types les plus courants

  • Nombre: Copie
  • Énumération: Copie
  • Structure/Classe: Référence immutable
  • Chaine de caractère: Référence immutable
  • Conteneur: Référence immutable
  • Template: Référence immutable
  • Stream: Référence immutable

Il est important de noter que cela reste des conseils généraux et non des règles strictes.

Top comments (0)