web-dev-qa-db-fra.com

Pourquoi l'initialisation de liste (à l'aide d'accolades) est-elle meilleure que les alternatives?

MyClass a1 {a};     // clearer and less error-prone than the other three
MyClass a2 = {a};
MyClass a3 = a;
MyClass a4(a);

Pourquoi?

Je ne pouvais pas trouver de réponse sur SO, alors laissez-moi répondre à ma propre question.

326
Oleksiy

Copier et coller à partir de Bjarne Stroustrup "La 4ème édition du langage de programmation C++" :

L'initialisation de la liste ne permet pas le rétrécissement (§iso.8.5.4). C'est:

  • Un entier ne peut pas être converti en un autre entier qui ne peut pas contenir sa valeur. Par exemple, char to int est autorisé, mais pas int to char.
  • Une valeur à virgule flottante ne peut pas être convertie en un autre type à virgule flottante qui ne peut pas contenir sa valeur. Par exemple, float to double est autorisé, mais pas double à float.
  • Une valeur à virgule flottante ne peut pas être convertie en un type entier.
  • Une valeur entière ne peut pas être convertie en un type à virgule flottante.

Exemple:

void fun(double val, int val2) {

    int x2 = val; // if val==7.9, x2 becomes 7 (bad)

    char c2 = val2; // if val2==1025, c2 becomes 1 (bad)

    int x3 {val}; // error: possible truncation (good)

    char c3 {val2}; // error: possible narrowing (good)

    char c4 {24}; // OK: 24 can be represented exactly as a char (good)

    char c5 {264}; // error (assuming 8-bit chars): 264 cannot be 
                   // represented as a char (good)

    int x4 {2.0}; // error: no double to int value conversion (good)

}

La situation seulement où = est préféré à {} est lorsqu’on utilise le mot clé auto pour obtenir le type déterminé par l’initialiseur.

Exemple:

auto z1 {99};   // z1 is an int
auto z2 = {99}; // z2 is std::initializer_list<int>
auto z3 = 99;   // z3 is an int

Conclusion

Préférez {} l’initialisation aux alternatives, sauf si vous avez une bonne raison de ne pas le faire.

294
Oleksiy

Il existe de nombreuses raisons d'utiliser l'initialisation d'accolade, mais vous devez savoir que le constructeur initializer_list<> est préféré aux autres constructeurs, l'exception étant le constructeur par défaut. Cela pose des problèmes avec les constructeurs et les modèles où le constructeur de type T peut être une liste d'initialiseurs ou un ancien ctor.

struct Foo {
    Foo() {}

    Foo(std::initializer_list<Foo>) {
        std::cout << "initializer list" << std::endl;
    }

    Foo(const Foo&) {
        std::cout << "copy ctor" << std::endl;
    }
};

int main() {
    Foo a;
    Foo b(a); // copy ctor
    Foo c{a}; // copy ctor (init. list element) + initializer list!!!
}

En supposant que vous ne rencontriez pas de telles classes, il y a peu de raisons de ne pas utiliser la liste d'intializer.

85
Red XIII

Il existe déjà d'excellentes réponses sur les avantages de l'initialisation de liste. Toutefois, ma règle personnelle est de NE PAS utiliser d'accolades autant que possible, mais plutôt de le rendre dépendant du sens conceptuel:

  • Si l'objet que je crée contient de manière conceptuelle les valeurs que je transmets dans le constructeur (conteneurs, structures POD, atomics, pointeurs intelligents, etc.), j'utilise les accolades.
  • Si le constructeur ressemble à un appel de fonction normal (il effectue des opérations plus ou moins complexes paramétrées par les arguments), j'utilise la syntaxe d'appel de fonction normale.
  • Pour l’initialisation par défaut, j’utilise toujours des accolades.
    Premièrement, de cette façon, je suis toujours sûr que l’objet est initialisé, qu’il soit par exemple est une "vraie" classe avec un constructeur par défaut qui serait appelé de toute façon ou un type intégré/POD. Deuxièmement, il est - dans la plupart des cas - cohérent avec la première règle, puisqu'un objet initialisé par défaut représente souvent un objet "vide".

D'après mon expérience, ce jeu de règles peut être appliqué de manière beaucoup plus cohérente que l'utilisation d'accolades par défaut, mais vous devez vous rappeler explicitement de toutes les exceptions lorsqu'elles ne peuvent pas être utilisées ou ont un sens différent de celui de la syntaxe d'appel de fonction "normale" avec parenthèse. (appelle une surcharge différente).

Il par exemple s'intègre parfaitement avec les types de bibliothèques standard tels que std::vector:

vector<int> a{10,20};   //Curly braces -> fills the vector with the arguments

vector<int> b(10,20);   //Parenthesis -> uses arguments to parametrize some functionality,                          
vector<int> c(it1,it2); //like filling the vector with 10 integers or copying a range.

vector<int> d{};      //empty braces -> default constructs vector, which is equivalent
                      //to a vector that is filled with zero elements
83
MikeMB