91 votes

Quand utiliser l'initialisateur entre accolades ?

En C++11, nous disposons d'une nouvelle syntaxe pour l'initialisation des classes qui nous offre un grand nombre de possibilités pour initialiser les 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, ce qui ralentit ma vitesse de codage. Je suis sûr que ce n'était pas l'intention de l'introduction des crochets.

Lorsqu'il s'agit d'un code de modèle, changer la syntaxe peut conduire à des significations différentes, c'est pourquoi il est essentiel de suivre la bonne voie.

Je me demande s'il existe une ligne directrice universelle pour le choix de la syntaxe.

63voto

celtschk Points 9699

I penser les éléments suivants pourraient constituer une bonne ligne directrice :

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

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

  • Si les valeurs utilisées pour l'initialisation sont no à stocker, mais décrire la valeur/l'état prévu(e) de l'objet, utiliser des parenthèses. Par exemple, l'argument de taille d'un vector ou l'argument du nom de fichier d'un fstream .

26voto

juanchopanza Points 115680

Je suis presque certain qu'il n'y aura jamais de ligne directrice universelle. Mon approche consiste à utiliser toujours des accolades en se rappelant que

  1. Les constructeurs de listes d'initialisation sont prioritaires sur les autres constructeurs.
  2. Tous les conteneurs de la bibliothèque standard et std::basic_string ont des constructeurs de liste d'initialisation.
  3. L'initialisation de l'accolade curly ne permet pas d'effectuer des conversions étroites.

Les accolades rondes et bouclées ne sont donc pas interchangeables. Mais le fait de connaître leurs différences me permet d'utiliser les accolades bouclées plutôt que les accolades rondes dans la plupart des cas (certains des cas où je ne peux pas le faire sont actuellement des bogues du compilateur).

16voto

Luc Danton Points 21421

En dehors du code générique (c'est-à-dire les modèles), vous pouvez (et je le fais) utiliser les accolades partout . L'un de ses avantages 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 des fonctions :

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, je n'accorde pas beaucoup d'attention à la différence entre les T t = { init }; o T t { init }; je trouve que la différence est mineure et qu'elle se traduira au pire par un message utile du compilateur sur l'utilisation abusive d'un style explicit constructeur.

Pour les types qui acceptent les std::initializer_list Bien qu'il soit évident que, parfois, le non std::initializer_list sont nécessaires (l'exemple classique étant std::vector<int> twenty_answers(20, 42); ). Il n'y a donc pas de problème à ne pas utiliser d'accolades.


En ce qui concerne le code générique (c'est-à-dire dans les modèles), ce dernier paragraphe aurait dû susciter quelques mises en garde. Prenons l'exemple suivant :

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

Dans ce cas 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 es std::string . Un signe très révélateur que quelque chose ne tourne pas rond dans cette affaire est qu'il y a non qui peut vous sauver ici (par exemple avec SFINAE) : std::is_constructible est en termes d'initialisation directe, alors que nous utilisons l'initialisation par accolade qui diffère de l'initialisation directe si et seulement si il n'y a pas de constructeur prenant std::initializer_list interférer. De même std::is_convertible n'est d'aucune utilité.

J'ai cherché à savoir s'il était possible de développer à la main une caractéristique permettant de résoudre ce problème, mais je ne suis pas très optimiste à ce sujet. Quoi qu'il en soit, je ne pense pas qu'il nous manquerait grand-chose, je pense que le fait que make_unique<T>(foo, bar) se traduisent par une construction équivalente à T(foo, bar) est très intuitif, surtout si l'on tient compte du fait que make_unique<T>({ foo, bar }) est assez différent et n'a de sens que si foo y bar ont le même type.

D'où pour le code générique, je n'utilise les accolades que pour l'initialisation des valeurs (par exemple T t {}; o T t = {}; ), ce qui est très pratique et, à mon avis, supérieur à la méthode C++03 T t = T(); . Sinon, il s'agit d'une syntaxe d'initialisation directe (c'est-à-dire T t(a0, a1, a2); ), ou parfois une construction par défaut ( T t; stream >> t; étant le seul cas où je l'utilise, je pense).

Cela ne signifie pas que tous Les accolades ne sont pas une bonne chose, comme le montre l'exemple précédent avec les 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)...) }; }

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

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