C++ : les différentes initialisations

En C++, différentes syntaxes permettent d’initialiser ses variables. Dans ce tuto, nous allons voir laquelle privilégier et pourquoi.

Plusieurs syntaxes permettent d’initialiser une variable :

type nom; // (1)
type nom(valeur); // (2)
type nom = valeur; // (3)
type nom{valeur}; // (4)
type nom = {valeur}; // (5)

Voyons quels sont les différences entre ces syntaxes. Pour cela, il faut faire la distinction entre les types fondamentaux et les classes.

Pour un type fondamental (int, double, …)

Pour une classe

Dans le cas où le type est une classe, c’est un peu plus compliqué. Pour plus de simplicité, prenons donc un exemple simple :

#include <iostream>
class A {
    public:
    A() {
        std::cerr << "défaut" << std::endl;
    }
    A(A const& a) : a{a.a} {
        std::cerr << "copie" << std::endl;
    }
    A(int a) : a{a} {
        std::cerr << "conversion" << std::endl;
    }
    private:
    int a{};
};

Les initialisations ont avec cette classe les comportements suivants :

Derniers mots

Vous l’aurez compris, pour éviter les mauvaises surprises, il faut donc privilégier la syntaxe avec les accolades (appelée initialisation uniforme).

Attention toutefois, si vous souhaitez construire par exemple un std::vector à 5 valeurs, il faut utiliser les parenthèses :

std::vector<int> monVector(5); // monVector peut contenir 5 valeurs

En effet, le code précédent n’est pas équivalent au code suivant (avec accolades) :

std::vector<int> monVector{5}; // monVector contient 1 valeur : 5

Pourquoi ? Il y a une subtilité à connaître :

S’il existe un constructeur par liste d’initialisation (c’est à dire avec comme paramètre une std::initializer_list), il est prioritaire face aux autres constructeurs compatibles.

Ainsi, dans notre exemple, l’initialisation uniforme d’un std::vector fait appel au constructeur par liste d’initialisation si les éléments de la liste sont du type du vector. En d’autres termes, les éléments entre accolades sont considérés comme étant les éléments à insérer dans le vector s’ils sont du bon type. Cela permet donc d’initialiser un std::vector avec en ensemble d’éléments :

std::vector<int> monVector{1,2,3};

Et ce n’est pas fini ! Cette initialisation uniforme a d’autres avantages :

Autres avantages de cette initialisation uniforme :

  • avec cette syntaxe, contrairement à la syntaxe avec parenthèses (2), on peut également initialiser à la valeur par défaut :

    int valeur{}; // valeur vaut 0
    A a{}; // appel du constructeur par défaut
    
  • et enfin, cette syntaxe peut être utilisée partout :

    • pour initialiser un attribut (ce qui n’est pas possible avec la syntaxe (2) :

      
      class B {
          private:
          int b{7}; // b vaut 7 par défaut
      }
      
    • pour initialiser un attribut dans la liste d’initialisation du constructeur (ce qui n’est pas possible avec la syntaxe (3) :

      class B {
          public:
          B() : b{7} {
              // b est initialisé à 7
          }
          private:
          int b;
      };
      
    • et enfin comme valeur de retour d’une fonction :

      A maFonction() {
          return {7};
      }
      

Conclusion

Pour être tranquille d’esprit, toujours privilégier l’initialisation uniforme (sauf lorsqu’un constructeur par liste d’initialisation existe et que l’on souhaite faire appel à un autre constructeur).