Quand utiliser l'initialiseur entre accolades?

94

En C ++ 11, nous avons cette nouvelle syntaxe pour l'initialisation des classes qui nous donne un grand nombre de possibilités pour initialiser des variables.

{ // Example 1
  int b(1);
  int a{1};
  int c = 1;
  int d = {1};
}
{ // Example 2
  std::complex<double> b(3,4);
  std::complex<double> a{3,4};
  std::complex<double> c = {3,4};
  auto d = std::complex<double>(3,4);
  auto e = std::complex<double>{3,4};
}
{ // Example 3
  std::string a(3,'x');
  std::string b{3,'x'}; // oops
}
{ // Example 4
  std::function<int(int,int)> a(std::plus<int>());
  std::function<int(int,int)> b{std::plus<int>()};
}
{ // Example 5
  std::unique_ptr<int> a(new int(5));
  std::unique_ptr<int> b{new int(5)};
}
{ // Example 6
  std::locale::global(std::locale("")); // copied from 22.4.8.3
  std::locale::global(std::locale{""});
}
{ // Example 7
  std::default_random_engine a {}; // Stroustrup's FAQ
  std::default_random_engine b;
}
{ // Example 8
  duration<long> a = 5; // Stroustrup's FAQ too
  duration<long> b(5);
  duration<long> c {5};
}

Pour chaque variable que je déclare, je dois réfléchir à la syntaxe d'initialisation que je dois utiliser et cela ralentit ma vitesse de codage. Je suis sûr que ce n'était pas l'intention d'introduire les accolades.

En ce qui concerne le code de modèle, la modification de la syntaxe peut avoir des significations différentes, il est donc essentiel de suivre la bonne voie.

Je me demande s'il existe une directive universelle sur la syntaxe à choisir.

Helami
la source
1
Un exemple de comportement involontaire de {} initialisation: string (50, 'x') vs string {50, 'x'} ici
P i

Réponses:

64

Je pense que ce qui suit pourrait être une bonne ligne directrice:

  • Si la valeur (unique) avec laquelle vous initialisez est destinée à être la valeur exacte de l'objet, utilisez l' =initialisation de copy ( ) (car en cas d'erreur, vous n'invoquerez jamais accidentellement un constructeur explicite, qui interprète généralement la valeur fournie différemment). Dans les endroits où l'initialisation de la copie n'est pas disponible, voyez si l'initialisation d'accolades a la sémantique correcte, et si c'est le cas, utilisez-la; sinon utilisez l'initialisation entre parenthèses (si cela n'est pas non plus disponible, vous n'avez pas de chance de toute façon).

  • Si les valeurs avec lesquelles vous initialisez sont une liste de valeurs à stocker dans l'objet (comme les éléments d'un vecteur / tableau ou d'une partie réelle / imaginaire d'un nombre complexe), utilisez l'initialisation d'accolades si disponible.

  • Si les valeurs avec lesquelles vous initialisez ne sont pas des valeurs à stocker, mais décrivent la valeur / l'état prévu de l'objet, utilisez des parenthèses. Les exemples sont l'argument de taille d'un vectorou l'argument de nom de fichier d'un fstream.

celtschk
la source
4
@ user1304032: Une locale n'est pas une chaîne, vous n'utiliserez donc pas l'initialisation de la copie. Une locale ne contient pas non plus de chaîne (elle peut stocker cette chaîne en tant que détail d'implémentation, mais ce n'est pas son but), vous n'utiliserez donc pas l'initialisation d'accolades. Par conséquent, la directive dit d'utiliser l'initialisation entre parenthèses.
celtschk
2
Personnellement, j'ai tout au plus aimé cette directive et cela fonctionne également bien sur le code générique. Il y a quelques exceptions ( T {}ou des raisons syntaxiques comme l' analyse la plus vexante ), mais en général, je pense que cela fait un bon conseil. Notez que c'est mon opinion subjective, donc on devrait aussi jeter un œil aux autres réponses.
helami
2
@celtschk: Cela ne fonctionnera pas pour les types non copiables et non déplaçables; type var{};Est-ce que.
ildjarn
2
@celtschk: Je ne dis pas que c'est quelque chose qui se produirait fréquemment, mais c'est moins de frappe et fonctionne dans plus de contextes, alors quel est l'inconvénient?
ildjarn
2
Mes directives n'appellent certainement jamais à une initialisation de copie. ; -]
ildjarn
26

Je suis convaincu qu'il n'y aura jamais de directive universelle. Mon approche est d'utiliser toujours des accolades bouclées en se souvenant que

  1. Les constructeurs de liste d'initialiseurs ont priorité sur les autres constructeurs
  2. Tous les conteneurs de bibliothèque standard et std :: basic_string ont des constructeurs de liste d'initialisation.
  3. L'initialisation des accolades ne permet pas de réduire les conversions.

Ainsi, les accolades rondes et bouclées ne sont pas interchangeables. Mais savoir où ils diffèrent me permet d'utiliser l'initialisation entre crochets bouclés dans la plupart des cas (certains des cas où je ne peux pas sont actuellement des bogues du compilateur).

juanchopanza
la source
6
Les accolades ont l'inconvénient que je peux appeler le constructeur de liste par erreur. Les parenthèses rondes ne le font pas. N'est-ce pas une raison pour utiliser les parenthèses par défaut?
helami
4
@user: Sur le, int i = 0;je ne pense pas que quiconque utiliserait int i{0}là-bas, et cela pourrait être déroutant (aussi, 0c'est si type int, donc il n'y aurait pas de rétrécissement ). Pour tout le reste, je suivrais les conseils de Juancho: préférez {}, méfiez-vous des quelques cas où vous ne devriez pas. Notez qu'il n'y a pas beaucoup de types qui prendront des listes d'initialiseurs comme arguments de constructeur, vous pouvez vous attendre à ce que les conteneurs et les types de type conteneur (tuple ...) les aient, mais la plupart du code appellera le constructeur approprié.
David Rodríguez - dribeas
3
@ user1304032 cela dépend si vous vous souciez du rétrécissement. Je le fais, donc je préfère que le compilateur me dise que int i{some floating point}c'est une erreur, plutôt que de tronquer silencieusement.
juanchopanza
3
Concernant "prefer {}, méfiez-vous des quelques cas où vous ne devriez pas": disons que deux classes ont un constructeur sémantiquement équivalent mais qu'une classe a aussi une liste d'initialiseurs. Les deux constructeurs équivalents doivent-ils être appelés différemment?
helami
3
@helami: "Disons que deux classes ont un constructeur sémantiquement équivalent mais qu'une classe a aussi une liste d'initialiseurs. Les deux constructeurs équivalents doivent-ils être appelés différemment?" Disons que je tombe sur l'analyse la plus vexante; cela peut arriver sur n'importe quel constructeur pour n'importe quelle instance. Il est beaucoup plus facile d'éviter cela si vous utilisez simplement {}pour signifier "initialiser" à moins que vous ne puissiez absolument pas .
Nicol Bolas
16

En dehors du code générique (c'est-à-dire des modèles), vous pouvez (et je le fais) utiliser des accolades partout . Un avantage est qu'il fonctionne partout, par exemple même pour l'initialisation en classe:

struct foo {
    // Ok
    std::string a = { "foo" };

    // Also ok
    std::string b { "bar" };

    // Not possible
    std::string c("qux");

    // For completeness this is possible
    std::string d = "baz";
};

ou pour les arguments de fonction:

void foo(std::pair<int, double*>);
foo({ 42, nullptr });
// Not possible with parentheses without spelling out the type:
foo(std::pair<int, double*>(42, nullptr));

Pour les variables auxquelles je ne prête pas beaucoup d'attention entre les styles T t = { init };ou T t { init };, je trouve que la différence est mineure et ne résultera au pire qu'en un message utile du compilateur sur une mauvaise utilisation d'un explicitconstructeur.

Pour les types qui acceptent std::initializer_listbien que de toute évidence, les non- std::initializer_listconstructeurs sont parfois nécessaires (l'exemple classique étant std::vector<int> twenty_answers(20, 42);). C'est bien de ne pas utiliser d'accolades alors.


En ce qui concerne le code générique (c'est-à-dire dans les modèles), ce tout dernier paragraphe aurait dû soulever quelques avertissements. Considérer ce qui suit:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T { std::forward<Args>(args)... } }; }

auto p = make_unique<std::vector<T>>(20, T {});Crée ensuite un vecteur de taille 2 si Test par exemple int, ou un vecteur de taille 20 si Test std::string. Un signe très révélateur qu'il se passe quelque chose de très mal ici est qu'il n'y a aucun trait qui peut vous sauver ici (par exemple avec SFINAE): std::is_constructiblec'est en termes d'initialisation directe, alors que nous utilisons l'initialisation d'accolades qui reporte à direct- initialisation si et seulement si aucun constructeur std::initializer_listn'interfère. De même std::is_convertiblen'est d'aucune aide.

J'ai cherché s'il était en fait possible de lancer manuellement un trait qui peut résoudre ce problème, mais je ne suis pas trop optimiste à ce sujet. En tout cas je ne pense pas qu'il nous manquerait grand-chose, je pense que le fait qu'il en make_unique<T>(foo, bar)résulte une construction équivalente à T(foo, bar)est très intuitif; d'autant plus que cela make_unique<T>({ foo, bar })est assez différent et n'a de sens que si fooet baront le même type.

Par conséquent, pour le code générique, je n'utilise que des accolades pour l'initialisation de la valeur (par exemple T t {};ou T t = {};), ce qui est très pratique et je pense supérieur à la méthode C ++ 03 T t = T();. Sinon, c'est soit la syntaxe d'initialisation directe (ie T t(a0, a1, a2);), soit parfois la construction par défaut ( T t; stream >> t;étant le seul cas où j'utilise cela je pense).

Cela ne signifie pas que toutes les accolades sont mauvaises, considérez l'exemple précédent avec des correctifs:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T(std::forward<Args>(args)...) }; }

Cela utilise toujours des accolades pour construire le std::unique_ptr<T>, même si le type réel dépend du paramètre de modèle T.

Luc Danton
la source
@interjay Certains de mes exemples peuvent en effet avoir besoin d'utiliser des types non signés à la place, par exemple make_unique<T>(20u, T {})pour Têtre soit unsignedou std::string. Pas trop sûr des détails. (Notez que j'ai également commenté les attentes concernant l'initialisation directe par rapport à l'initialisation d'accolades concernant par exemple les fonctions de transfert parfait.) std::string c("qux");N'a pas été spécifié pour fonctionner comme une initialisation en classe pour éviter les ambiguïtés avec les déclarations de fonctions membres dans la grammaire.
Luc Danton
@interjay Je ne suis pas d'accord avec vous sur le premier point, n'hésitez pas à vérifier 8.5.4 Initialisation de liste et 13.3.1.7 Initialisation par initialisation de liste. En ce qui concerne le second, vous devez regarder de plus près ce que j'ai écrit (qui concerne l' initialisation en classe ) et / ou la grammaire C ++ (par exemple, membre-déclarateur , qui fait référence à l' accolade-ou-égal-initialiseur ).
Luc Danton
Hmm, vous avez raison - j'ai testé avec GCC 4.5 plus tôt, ce qui semblait confirmer ce que je disais, mais GCC 4.6 est d'accord avec vous. Et j'ai manqué le fait que vous parliez d'initialisation en classe. Mes excuses.
entre le