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.
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
.
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
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).
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
.