Pourquoi l’initialisation de la liste (en utilisant des 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 n’ai pas trouvé de réponse sur SO, alors laissez-moi répondre à ma propre question.

Fondamentalement, copier et coller à partir de “The C ++ Programming Language 4e édition” de Bjarne Stroustrup:

L’initialisation de la liste ne permet pas de 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 pour 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 pour flotter.
  • Une valeur en virgule flottante ne peut pas être convertie en 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 seule situation où = est préférable à {} consiste à utiliser auto mot auto clé auto pour obtenir le type déterminé par l’initialiseur.

Exemple:

 auto z1 {99}; // z1 is an initializer_list auto z2 = 99; // z2 is an int 

Conclusion

Préférez l’initialisation {} par rapport aux alternatives à moins que vous ayez de bonnes raisons de ne pas le faire.

Il y a BEAUCOUP de raisons d’utiliser l’initialisation des accolades, 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 conduit à des problèmes avec les constructeurs et les modèles où le constructeur de type T peut être soit une liste d’initialisation, soit un ancien script classique.

 struct Foo { Foo() {} Foo(std::initializer_list) { 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 rencontrez pas de telles classes, il y a peu de raisons de ne pas utiliser la liste d'initialisation.

Il y a déjà de bonnes réponses sur les avantages de l’utilisation de l’initialisation des listes, cependant ma règle de base personnelle n’est PAS d’utiliser des accolades lorsque cela est possible, mais plutôt de les rendre dépendantes de la signification conceptuelle:

  • Si l’object que je crée contient conceptuellement les valeurs que je transmets au constructeur (par exemple, conteneurs, structures POD, atomiques, 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.
    D’une part, de cette façon, je suis toujours sûr que l’object est initialisé indépendamment du fait qu’il s’agisse par exemple d’une “vraie” classe avec un constructeur par défaut appelé de toute façon ou d’un type intégré / POD. Deuxièmement, il est – dans la plupart des cas – cohérent avec la première règle, car un object initialisé par défaut représente souvent un object “vide”.

D’après mon expérience, cet ensemble de règles peut être appliqué de manière beaucoup plus cohérente que l’utilisation d’accolades par défaut, mais il faut se rappeler explicitement toutes les exceptions quand elles ne peuvent pas être utilisées ou avoir une signification différente de la syntaxe (appelle une surcharge différente).

Il convient par exemple parfaitement aux types de bibliothèque standard tels que std::vector :

 vector a{10,20}; //Curly braces -> fills the vector with the arguments vector b(10,20); //Parentesis -> uses arguments to parameterize some functionality, vector c(it1,it2); //like filling the vector with 10 integers or copying a range. vector d{}; //empty braces -> default constructs vector, which is equivalent //to a vector that is filled with zero elements