web-dev-qa-db-fra.com

Quand utiliser l'initialiseur entre accolades?

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 à 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 du modèle, la modification de la syntaxe peut conduire à des significations différentes, il est donc essentiel d'aller dans le bon sens.

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

90
helami

I pensez ce qui suit pourrait être une bonne ligne directrice:

  • Si la valeur (unique) avec laquelle vous initialisez est destinée à être valeur exacte de l'objet, utilisez copy (=) initialisation (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 copie n'est pas disponible, voyez si l'initialisation de l'accolade a la sémantique correcte, et si oui, utilisez-la; sinon, utilisez l'initialisation entre parenthèses (si ce n'est pas également disponible, vous n'avez pas de chance de toute façon).

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

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

61
celtschk

Je suis presque sûr qu'il n'y aura jamais de directive universelle. Mon approche consiste à utiliser des accolades toujours bouclées en se souvenant que

  1. Les constructeurs de liste d'initialisation 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.

Les accolades rondes et bouclées ne sont donc pas interchangeables. Mais savoir où ils diffèrent me permet d'utiliser l'initialisation des parenthèses bouclées sur rondes dans la plupart des cas (certains des cas où je ne peux pas sont actuellement des bogues du compilateur).

25
juanchopanza

En dehors du code générique (c'est-à-dire des modèles), vous pouvez (et je 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 n'accorde pas beaucoup d'attention entre les styles T t = { init }; Ou T t { init };, Je trouve la différence mineure et, au pire, n'entraînera qu'un message utile du compilateur sur l'utilisation abusive d'un explicit constructeur.

Pour les types qui acceptent std::initializer_list, Bien que parfois, les constructeurs non std::initializer_list Sont nécessaires (l'exemple classique étant std::vector<int> twenty_answers(20, 42);). C'est bien de ne pas utiliser d'appareil dentaire alors.


En ce qui concerne le code générique (c'est-à-dire dans les modèles), le tout dernier paragraphe aurait dû déclencher des 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)... } }; }

Puis auto p = make_unique<std::vector<T>>(20, T {}); crée un vecteur de taille 2 si T est par exemple int, ou un vecteur de taille 20 si T est 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_constructible Est en termes d'initialisation directe, alors que nous utilisons l'initialisation d'accolade qui diffère de l'initialisation directe si et seulement si il n'y a pas de constructeur prendre std::initializer_list interférer. De même, std::is_convertible Ne sert à rien.

J'ai vérifié s'il était en fait possible de lancer 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 que nous manquerions beaucoup, je pense que le fait que make_unique<T>(foo, bar) aboutisse à une construction équivalente à T(foo, bar) est très intuitif; d'autant plus que make_unique<T>({ foo, bar }) est assez différent et n'a de sens que si foo et bar ont le même type.

Par conséquent pour le code générique, j'utilise uniquement 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 manière C++ 03 T t = T();. Sinon c'est soit la syntaxe d'initialisation directe (c'est-à-dire T t(a0, a1, a2);), soit parfois la construction par défaut (T t; stream >> t; Étant le seul cas où j'utilise ça je pense).

Cela ne signifie pas que toutes les accolades sont cependant 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.

16
Luc Danton