C++ : presque toujours auto (AAA)

Rédigé par Nicolas K 1 commentaire

Le mot clé auto est apparu en C++11 mais a parfois encore une mauvaise image, à tort ! Car en effet, il y a beaucoup d’avantages à utiliser auto au maximum. Cet article va détailler comment s’utilise ce mot clé pour déclarer une variable locale et pourquoi il est toujours préférable de déclarer ses variables locales avec ce mot clé.

Présentation

Le mot clé auto permet (entre autre) de déclarer une variable locale sans écrire son type :

auto entier{7};

Ici, la variable entier est un int qui vaut 7. Mais le C++ reste un langage fortement typé ! La variable ne peut avoir qu’un type tout au long de son existence. Le code suivant ne compile donc pas :

auto entier{7};
entier = "erreur"; // erreur de compilation : entier est un int

Avec le mot clé auto, le compilateur déduit donc le type de variable à partir de la valeur donnée lors de l’initialisation. Puisque 7 est un entier de type int, entier est de type int.

Inutile de préciser que puisque le type est déduit de la valeur d’initialisation, cette initialisation est obligatoire. Le code suivant ne compile donc pas :

auto variable; // erreur de compilation

Enfin, une subtilité à connaître :

Attention : le type déduit correspond au type de la valeur sans les spécificateurs const, volatile et &.
Exemple :

volatile int const& a{7};
auto b{a};

Dans le code précédent, b est du type int (et pas volatile int const& comme a).

Si on souhaite ajouter des spécificateurs, on peut toujours le faire en les ajoutant à la déclaration :

volatile auto const& b{a};

En anglais, on retrouve parfois le sigle AAA pour Almost Always Auto. En français : Presque toujours auto. L’idée est simple : utiliser au maximum ce mot clé auto. Mais pourquoi ça ?

Pourquoi toujours déclarer ses variables locales avec auto ?

Voyons les principaux avantages à travers quelques exemples.

  1. On l’a vu juste au dessus, ça oblige à initialiser ses variables (ce qui est très important, nous le savons tous).
  2. Ça évite les conversions implicites puisque le type déduit est forcément le type de la valeur. Typiquement :
    std::vector vecteur{};
    // plusieurs lignes de code qui remplissent le vecteur […]
    int taille = vecteur.size();

    Ce code compile et fonctionne… dans la majorité des cas. Mais le type de retour de vecteur.size() n’est pas un int et rien dans la norme n’impose que la valeur de retour puisse rentrer dans une variable de type int. En d’autres termes, selon l’implémentation, ce code peut poser un problème lorsque le vecteur contient beaucoup d’éléments. Il s’agit donc là typiquement du genre d’erreur qui ne se reproduit pas à chaque fois et qui est très difficile à déceler !

    Avec auto, il n’y a plus de problème, la variable prend automatiquement le type de la valeur d’initialisation :

    auto taille = vecteur.size();
  3. Toujours dans la rubrique "conversions implicites", une autre conséquence est que ça garantit que la variable est directement initialisée avec la valeur donnée et qu’il n’y a pas d’objet temporaire créé. Prenons un exemple :
    ColorHSL getDefaultColor() {
        return {171,0.50,0.23};
    }
    
    int main()
    {
        Color couleur = getDefaultColor();
        // […] suite du code
    }

    Dans l’exemple ci-dessus, l’appel à getDefaultColor() va créer un objet temporaire de type ColorHSL qui va ensuite être converti en Color (si cette conversion implicite est possible). Si on peut travailler indifféremment avec un objet de type ColorHSL ou avec un objet de type Color dans la suite du code, utiliser auto permet d’éviter de telles conversions contre-performantes :

    auto couleur = getDefautColor();

    Et si c’est voulu de convertir en Color, on peut toujours le faire explicitement (ce qui permet de mettre en évidence le fait que c’est volontaire) :

    auto couleur = Color{getDefaultColor()};
  4. Ça évite de devoir réfléchir aux types complexes dont on se fiche mais qui sont nécessaires pour certaines variables temporaires (qui n’étaient là avant C++11 que pour des raisons techniques, on est d’accord qu’ils n’apportent rien !). Typiquement les itérateurs :
    std::map<int, std::string> maMap{};
    
    // sans auto :
    std::map<int, std::string>::iterator debut = maMap.begin();
    
    // avec auto :
    auto debut = maMap.begin();
    
  5. Ça permet d’avoir un code plus générique. Dans ce même exemple, si on décide un jour de passer à une map constante :
    const std::map<int, std::string> maMap{};
    
    // sans auto : ne compile plus
    std::map<int, std::string>::iterator debut = maMap.begin();
    
    // avec auto : compile toujours
    auto debut = maMap.begin();

    La déclaration sans auto ne compile plus (car maMap.begin() retourne maintenant un std::map<int, std::string>::const_iterator et non plus un std::map<int, std::string>::iterator. Alors que celle avec auto fonctionne toujours.

  6. Ça apporte une certaine homogénéité de la syntaxe avec les autres déclarations : nom à gauche, type/valeur à droite.

    auto allocationStatique = MaClasse{4};
    auto allocationDynamique = new MaClasse(4);
  7. Et enfin, c’est également plus rapide à écrire et plus lisible !

Conclusion

Pour conclure, utiliser auto pour déclarer ses variables locales apporte au code :
  • robustesse (points 1,2)
  • performance (point 3)
  • maintenabilité (points 4,5,6,7)

Alors pourquoi s’en priver ?

Classé dans : Tutos Mots clés : aucun

1 commentaire

Les commentaires sont fermés.

Fil RSS des commentaires de cet article