Le style de codage est finalement subjectif, et il est hautement improbable que des avantages substantiels en termes de performances en découlent. Mais voici ce que je dirais que l'utilisation libérale de l'initialisation uniforme vous apporte:
Minimise les noms de types redondants
Considérer ce qui suit:
vec3 GetValue()
{
return vec3(x, y, z);
}
Pourquoi dois-je taper vec3
deux fois? Y at-il un point à cela? Le compilateur sait bien ce que la fonction retourne. Pourquoi ne puis-je pas simplement dire: "appelle le constructeur de ce que je retourne avec ces valeurs et le renvoie?" Avec une initialisation uniforme, je peux:
vec3 GetValue()
{
return {x, y, z};
}
Tout fonctionne.
Encore mieux est pour les arguments de fonction. Considère ceci:
void DoSomething(const std::string &str);
DoSomething("A string.");
Cela fonctionne sans avoir à taper un nom de type, car std::string
sait se construire de manière const char*
implicite. C'est génial. Mais que se passe-t-il si cette chaîne provient de RapidXML? Ou une chaîne de Lua. C'est-à-dire, disons que je connais réellement la longueur de la chaîne à l'avant. Le std::string
constructeur qui prend un const char*
devra prendre la longueur de la chaîne si je passe juste un const char*
.
Il existe cependant une surcharge qui prend explicitement une longueur. Mais pour l' utiliser, je dois le faire: DoSomething(std::string(strValue, strLen))
. Pourquoi le nom de type supplémentaire est-il là? Le compilateur sait quel est le type. Tout comme avec auto
, nous pouvons éviter d'avoir des noms de caractères supplémentaires:
DoSomething({strValue, strLen});
Ça fonctionne. Pas de noms de famille, pas de chichi, rien. Le compilateur fait son travail, le code est plus court et tout le monde est content.
Certes, il y a des arguments à faire pour que la première version ( DoSomething(std::string(strValue, strLen))
) soit plus lisible. En clair, ce qui se passe et qui fait quoi est évident. C'est vrai dans une certaine mesure. comprendre le code uniforme basé sur l'initialisation nécessite d'examiner le prototype de fonction. C'est la même raison pour laquelle certains disent que vous ne devriez jamais transmettre de paramètres par référence non const: vous pouvez ainsi voir sur le site de l'appel si une valeur est en cours de modification.
Mais la même chose pourrait être dite pour auto
; savoir ce que vous obtenez auto v = GetSomething();
nécessite de regarder la définition de GetSomething
. Mais cela n’a pas cessé auto
d’être utilisé avec un abandon presque téméraire une fois que vous y avez accès. Personnellement, je pense que ça ira une fois que vous vous y serez habitués. Surtout avec un bon IDE.
Ne jamais obtenir le plus vexant parse
Voici du code.
class Bar;
void Func()
{
int foo(Bar());
}
Quiz Pop: c'est quoi foo
? Si vous avez répondu "une variable", vous avez tort. C'est en fait le prototype d'une fonction qui prend comme paramètre une fonction qui retourne un Bar
, et la foo
valeur de retour de la fonction est un int.
Cela s'appelle "La plupart des analyses vexantes" de C ++, car cela n'a aucun sens pour un être humain. Mais les règles de C ++ exigent malheureusement cela: si cela peut éventuellement être interprété comme un prototype de fonction, alors ce sera le cas. Le problème est Bar()
; cela pourrait être l'une des deux choses. Il pourrait s'agir d'un type nommé Bar
, ce qui signifie qu'il crée un temporaire. Ou cela pourrait être une fonction qui ne prend aucun paramètre et retourne un Bar
.
L’initialisation uniforme ne peut pas être interprétée comme un prototype de fonction:
class Bar;
void Func()
{
int foo{Bar{}};
}
Bar{}
crée toujours un temporaire. int foo{...}
crée toujours une variable.
Il existe de nombreux cas où vous souhaitez utiliser Typename()
mais que vous ne pouvez tout simplement pas utiliser à cause des règles d'analyse de C ++. Avec Typename{}
, il n'y a pas d'ambiguïté.
Raisons pour ne pas
Le seul pouvoir réel que vous abandonnez est le rétrécissement. Vous ne pouvez pas initialiser une valeur plus petite avec une valeur plus grande avec une initialisation uniforme.
int val{5.2};
Cela ne compilera pas. Vous pouvez le faire avec une initialisation à l’ancienne, mais pas une initialisation uniforme.
Cela a été fait en partie pour que les listes d’initialisation fonctionnent réellement. Sinon, il y aurait beaucoup de cas ambigus en ce qui concerne les types de listes d'initialiseurs.
Bien sûr, certains pourraient soutenir qu'un tel code mérite de ne pas être compilé. Je suis personnellement d'accord rétrécir est très dangereux et peut conduire à un comportement désagréable. Il est probablement préférable de détecter ces problèmes dès le début du compilateur. À tout le moins, le rétrécissement suggère que quelqu'un ne pense pas trop au code.
Notez que les compilateurs vous préviendront généralement de ce genre de chose si votre niveau d’alerte est élevé. En réalité, tout cela fait de l’avertissement une erreur forcée. Certains pourraient dire que vous devriez le faire de toute façon;)
Il y a une autre raison de ne pas:
std::vector<int> v{100};
Qu'est-ce que cela fait? Il pourrait créer un vector<int>
avec cent éléments construits par défaut. Ou il pourrait créer un vector<int>
élément avec 1 élément dont la valeur est 100
. Les deux sont théoriquement possibles.
En réalité, il fait le dernier.
Pourquoi? Les listes d'initialiseur utilisent la même syntaxe que l'initialisation uniforme. Il doit donc y avoir des règles pour expliquer quoi faire en cas d’ambiguïté. La règle est assez simple: si le compilateur peut utiliser un constructeur de liste d'initialisation avec une liste initialisée par accolade, alors ce sera le cas . Puisqu'il vector<int>
a un constructeur de liste d'initialisation qui prend initializer_list<int>
, et que {100} pourrait être valide initializer_list<int>
, il doit donc l' être .
Pour obtenir le constructeur de dimensionnement, vous devez utiliser à la ()
place de {}
.
Notez que s'il s'agissait vector
de quelque chose qui n'était pas convertible en entier, cela ne se produirait pas. Une liste d'initialisation ne correspondrait pas au constructeur de liste d'initialisateurs de ce vector
type, et le compilateur serait donc libre de choisir parmi les autres constructeurs.