618 votes

Quels sont les avantages de l'initialisation d'une liste (en utilisant des accolades) ?

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

Pourquoi ?

13 votes

Pourquoi ne pas utiliser auto ?

1 votes

@MarkGarcia Huh ? Veuillez préciser :)

4 votes

Cela n'irait pas dans la boîte à commentaires ;). Quoi qu'il en soit, pour citer l'article en question : "...les principales raisons de déclarer des variables en utilisant auto sont la correction, la performance, la maintenabilité et la robustesse - et, oui, la commodité..." .

539voto

Oleksiy Points 3145

Il s'agit essentiellement d'un copier-coller de l'ouvrage de Bjarne Stroustrup intitulé "Le langage de programmation C++ 4e édition" :

Initialisation de la liste ne permet pas le rétrécissement (§iso.8.5.4). C'est-à-dire que

  • Un nombre entier ne peut pas être converti en un autre nombre entier qui ne peut pas conserver sa valeur. Par exemple, le char en int est autorisé, mais pas int en char.
  • Une valeur en virgule flottante ne peut pas être convertie en un autre type de virgule flottante qui ne peut pas conserver sa valeur. valeur. Par exemple, la conversion de float en double est autorisée, mais pas celle de double en 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)

}

En sólo La situation dans laquelle = est préféré à {} est celle où l'on utilise auto pour obtenir le type déterminé par l'initialisateur.

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, à moins que vous n'ayez une bonne raison de ne pas le faire.

62 votes

Il y a aussi le fait qu'utiliser () peut être analysé comme une déclaration de fonction. Il est confus et incohérent que vous puissiez dire T t(x,y,z); mais pas T t() . Et parfois, vous êtes certain x tu ne peux même pas dire T t(x); .

131 votes

Je ne suis pas du tout d'accord avec cette réponse ; l'initialisation entre accolades devient un désordre complet quand vous avez des types avec un ctor acceptant un std::initializer_list . RedXIII mentionne cette question (et la balaie d'un revers de main), alors que vous l'ignorez complètement. A(5,4) y A{5,4} peuvent appeler des fonctions complètement différentes, et c'est une chose importante à savoir. Cela peut même donner lieu à des appels qui semblent peu intuitifs. En disant que vous devriez préférer {} par défaut, les gens comprendront mal ce qui se passe. Ce n'est pas votre faute, cependant. Je pense personnellement que c'est une fonctionnalité extrêmement mal pensée.

22 votes

@user1520427 C'est pourquoi il y a le " sauf si vous avez une bonne raison de ne pas le faire partie ".

192voto

MikeMB Points 5827

Il y a déjà d'excellentes réponses sur les avantages de l'utilisation de l'initialisation de liste, mais ma règle personnelle est de NE PAS utiliser d'accolades chaque fois que cela est possible, mais plutôt de le faire dépendre de la signification conceptuelle :

  • Si l'objet que je crée contient conceptuellement les valeurs que je passe dans le constructeur (par exemple, les conteneurs, les structures POD, les atomiques, les pointeurs intelligents, etc.
  • Si le constructeur ressemble à un appel de fonction normal (il effectue des opérations plus ou moins complexes qui sont paramétrées par les arguments), alors j'utilise la syntaxe normale d'appel de fonction.
  • Pour l'initialisation par défaut, j'utilise toujours des accolades.
    D'abord, de cette façon, je suis toujours sûr que l'objet est initialisé, qu'il s'agisse d'une "vraie" classe avec un constructeur par défaut qui serait appelé de toute façon ou d'un type POD. Deuxièmement, cela est - dans la plupart des cas - cohérent avec la première règle, car un objet initialisé par défaut représente souvent un objet "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 des accolades par défaut, mais en devant se souvenir explicitement de toutes les exceptions lorsqu'elles ne peuvent pas être utilisées ou ont une signification différente de la syntaxe "normale" d'appel de fonction avec parenthèses (appelle une surcharge différente).

Il s'adapte bien, par exemple, aux types de bibliothèque standard tels que std::vector :

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

vector<int> b(10, 20);   //Parentheses -> 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

120voto

Red XIII Points 1247

Il existe de NOMBREUSES raisons d'utiliser l'initialisation en accolade, mais vous devez savoir que le site initializer_list<> est préféré aux autres constructeurs à l'exception du constructeur par défaut. Cela entraîne des problèmes avec les constructeurs et les modèles où le type T Le constructeur peut être soit une liste d'initialisateurs, soit un simple 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'initialisation.

24 votes

Il s'agit d'un très point important de la programmation générique. Lorsque vous écrivez des modèles, no utiliser des listes d'initialisation entre crochets (nom donné par la norme à l'expression { ... } ), à moins que vous ne souhaitiez que le initializer_list sémantique (enfin, et peut-être pour la construction par défaut d'un objet).

108 votes

Honnêtement, je ne comprends pas pourquoi le std::initializer_list La règle n'existe même pas - elle ne fait qu'ajouter de la confusion et du désordre au langage. Qu'y a-t-il de mal à faire Foo{{a}} si vous voulez que le std::initializer_list constructeur ? Cela semble beaucoup plus facile à comprendre que d'avoir std::initializer_list ont la priorité sur toutes les autres surcharges.

6 votes

+1 pour le commentaire ci-dessus, car c'est vraiment le bordel je pense ! !! ce n'est pas logique ; Foo{{a}} suit une certaine logique pour moi bien plus que Foo{a} qui se transforme en une préséance de la liste des initiales (l'utilisateur pourrait penser hm...)

11voto

codeling Points 6542

Mise à jour (2022-02-11) : Notez qu'il existe des opinions plus récentes à ce sujet que celle postée à l'origine (ci-dessous), qui plaident contre la préférence de l'initialisateur {}, comme par exemple Arthur Dwyer dans son billet de blog sur Le cauchemar de l'initialisation en C++ .

Réponse originale :

Lire Herb Sutter's (mis à jour) GotW #1 . Ceci explique en détail la différence entre ces options, et quelques autres, ainsi que plusieurs problèmes pertinents pour distinguer le comportement des différentes options.

Le gist/copié de la section 4 :

Quand faut-il utiliser la syntaxe ( ) ou { } pour initialiser des objets ? Et pourquoi ? Voici une directive simple :

Ligne directrice : Préférez utiliser l'initialisation avec { }, comme le vecteur v = { 1, 2, 3, 4 } ; ou auto v = vector{ 1, 2, 3, 4 } ;, car car c'est plus cohérent, plus correct et évite d'avoir à connaître les pièges des les pièges de l'ancien style. Dans les cas à argument unique où vous préférez voir uniquement le signe =, comme int i = 42 ; et auto x = n'importe quoi ; l'omission des accolades ne pose aucun problème.

Cela couvre la grande majorité des cas. Il n'y a qu'une seule grande exception :

Dans de rares cas, comme le vecteur v(10,20) ; ou auto v = vecteur(10,20) ;, utilisez l'initialisation avec ( ) pour appeler explicitement un constructeur qui est autrement caché par un constructeur initializer_list constructeur.

Cependant, la raison pour laquelle cela devrait être généralement " rare " est que les défauts et la construction par copie sont déjà spéciales et fonctionnent bien avec { }, et que une bonne conception de classe évite maintenant la plupart du temps le cas de recours à( ) pour les constructeurs définis par l'utilisateur. constructeurs définis par l'utilisateur grâce à cette directive finale de conception :

Ligne directrice : Quand vous concevez une classe, évitez de fournir un constructeur qui surcharge de manière ambiguë avec un constructeur initializer_list, afin que les utilisateurs n'auront pas besoin d'utiliser ( ) pour atteindre un tel constructeur caché.

Voir également les directives de base à ce sujet : ES.23 : Préférer la syntaxe {}-initialisateur .

6voto

Allan Jensen Points 149

C'est seulement plus sûr tant que vous ne construisez pas avec -Wno-narrowing comme le fait Google dans Chromium. Si vous le faites, alors il est MOINS sûr. Sans ce drapeau, les seuls cas non sûrs seront corrigés par C++20.

Note : A) Les parenthèses bouclées sont plus sûres car elles ne permettent pas de rétrécir. B) Les accolades sont moins sûres car elles permettent de contourner les constructeurs privés ou supprimés, et d'appeler implicitement les constructeurs marqués explicites.

Ces deux éléments combinés signifient qu'ils sont plus sûrs si ce qui est à l'intérieur est une constante primitive, mais moins sûrs s'il s'agit d'objets (bien que cela ait été corrigé en C++20).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X