Voici 8 manières de déclarer et d’initialiser des tableaux en C ++ 11 qui semblent bien fonctionner sous g++
:
/*0*/ std::array arr0({1, 2, 3}); /*1*/ std::array arr1({{1, 2, 3}}); /*2*/ std::array arr2{1, 2, 3}; /*3*/ std::array arr3{{1, 2, 3}}; /*4*/ std::array arr4 = {1, 2, 3}; /*5*/ std::array arr5 = {{1, 2, 3}}; /*6*/ std::array arr6 = std::array({1, 2, 3}); /*7*/ std::array arr7 = std::array({{1, 2, 3}});
Quelles sont les bonnes selon le standard ssortingct (et le futur standard C ++ 14)? Quelles sont les plus courantes / utilisées et celles à éviter (et pour quelle raison)?
C ++ 11 summary / TL; DR
std::array
contient un tableau brut. Par conséquent, les exemples 1, 3, 5 et 7 ne sont pas obligés de fonctionner. Cependant, je ne connais pas d’implémentation d’une bibliothèque standard où elles ne fonctionnent pas (en pratique). std::array arr4 = {1, 2, 3};
Je préférerais la version 4 ou la version 2 (avec le correctif d’éloignement de brace), car elles s’initialisent directement et sont nécessaires / susceptibles de fonctionner.
Pour le style AAA de Sutter, vous pouvez utiliser auto arrAAA = std::array
, mais cela nécessite le correctif d’élision de l’attelle.
std::array
doit être un agrégat [array.overview] / 2, ce qui implique qu’il n’a pas de constructeur fourni par l’utilisateur (c’est-à-dire que par défaut, copy, move et ctor).
std::array arr0({1, 2, 3}); std::array arr1({{1, 2, 3}});
Une initialisation avec (..)
est une initialisation directe. Cela nécessite un appel du constructeur. Dans le cas de arr0
et arr1
, seuls les constructeurs copier / déplacer sont viables. Par conséquent, ces deux exemples signifient créer un std::array
temporaire à partir de la liste initialisée, et le copier / déplacer vers la destination . Grâce à l’élision copie / déplacement, le compilateur est autorisé à annuler cette opération de copie / déplacement, même si elle a des effets secondaires.
NB même si les valeurs temporaires sont des valeurs, il peut invoquer une copie (sémantiquement, avant élision de la copie) car le ctor de déplacement de std::array
pourrait ne pas être déclaré implicitement, par exemple s’il était supprimé.
std::array arr6 = std::array({1, 2, 3}); std::array arr7 = std::array({{1, 2, 3}});
Ce sont des exemples d’initialisation de copie. Il y a deux temporaires créés:
{1, 2, 3}
pour appeler le constructeur copier / déplacer std::array(..)
ce dernier temporaire est ensuite copié / déplacé vers la variable de destination nommée. La création des deux temporaires peut être élidée.
Autant que je sache, une implémentation pourrait écrire un explicit array(array const&) = default;
constructeur et ne pas violer la norme; cela rendrait ces exemples mal formés. (Cette possibilité est exclue par [conteneur.requirements.general], félicitations à David Krauss, voir cette discussion .)
std::array arr2{1, 2, 3}; std::array arr3{{1, 2, 3}}; std::array arr4 = {1, 2, 3}; std::array arr5 = {{1, 2, 3}};
Ceci est une initialisation globale. Ils initialisent tous “directement” le std::array
, sans appeler un constructeur de std::array
et sans créer (sémantiquement) un tableau temporaire. Les membres de std::array
sont initialisés par copie-initialisation (voir ci-dessous).
Sur le sujet de la prothèse-élision:
Dans la norme C ++ 11, l’élision entre accolades ne s’applique qu’aux déclarations de la forme T x = { a };
mais pas à T x { a };
. Ceci est considéré comme un défaut et sera corrigé en C ++ 1y. Cependant, la résolution proposée ne fait pas partie du standard (statut du DRWP, voir en haut de la page liée) et vous ne pouvez donc pas compter sur votre compilateur pour le mettre en œuvre également pour T x { a };
.
Par conséquent, std::array
(exemples 0, 2, 6) sont mal formés, à proprement parler. Pour autant que je sache, les versions récentes de clang ++ et de g ++ permettent l’élision de l’accolade dans T x { a };
déjà.
Dans l’exemple 6, std::array
utilise l’initialisation de copie: l’initialisation pour la transmission d’argument est également copy-init. Cependant, la ressortingction défectueuse de l’élision entre accolades, “dans une déclaration de la forme T x = { a };
“ , interdit également l’élision entre accolades, car il ne s’agit pas d’une déclaration et certainement pas de cette forme.
Au sujet de l’initialisation des agrégats:
Comme le souligne Johannes Schaub dans un commentaire , il est uniquement garanti que vous pouvez initialiser un std::array
avec la syntaxe suivante [array.overview] / 2:
tableaua = { liste d'initialisation };
Vous pouvez en déduire que si accolade est autorisée sous la forme T x { a };
, que la syntaxe
tableaua { initializer-list };
est bien formé et a la même signification. Cependant, il n’est pas garanti que std::array
contienne réellement un tableau brut en tant que son seul membre de données (voir aussi LWG 2310 ). Je pense qu’un exemple pourrait être une spécialisation partielle std::array
, où il y a deux données membres T m0
et T m1
. Par conséquent, on ne peut pas conclure que
tableaua {{ initializer-list }};
est bien formé. Cela conduit malheureusement à la situation où il n’existe aucun moyen garanti d’initialiser une élision temporaire std::array
sans attelle pour T x { a };
, et signifie également que les exemples impairs (1, 3, 5, 7) ne sont pas obligés de fonctionner.
Toutes ces méthodes pour initialiser un std::array
mènent éventuellement à une initialisation d’agrégat. Il est défini comme une initialisation de copie des membres agrégés. Toutefois, l’initialisation de copie à l’aide d’une liste d’initialisation barrée peut toujours initialiser directement un membre agrégé. Par exemple:
struct foo { foo(int); foo(foo const&)=delete; }; std::array arr0 = {1, 2}; // error: deleted copy-ctor std::array arr1 = {{1}, {2}}; // error/ill-formed, cannot initialize a // possible member array from {1} // (and too many initializers) std::array arr2 = {{{1}, {2}}}; // not guaranteed to work
La première tente d’initialiser les éléments du tableau à partir des clauses initializer 1
et 2
, respectivement. Cette copie-initialisation est équivalente à foo arr0_0 = 1;
qui à son tour équivaut à foo arr0_0 = foo(1);
ce qui est illégal (copieur supprimé).
La seconde ne contient pas une liste d’expressions, mais une liste d’initialisateurs. Par conséquent, elle ne répond pas aux exigences de [array.overview] / 2. En pratique, std::array
contient un membre de données de tableau brut, qui serait initialisé (uniquement) à partir de la première clause d’initialisation {1}
, la seconde clause {2}
est alors illégale.
Le troisième problème est le même que le second: cela fonctionne s’il existe un membre de données de tableau, mais cela n’est pas garanti.
Je crois qu’ils sont tous ssortingctement conformes, sauf éventuellement arr2
. Je choisirais la méthode arr3
, car elle est concise, claire et définitivement valable. Si arr2
est valide (je ne suis pas sûr), ce serait encore mieux, en fait.
La combinaison des parens et des accolades (0 et 1) ne me convient jamais parfaitement, les égaux (4 et 5) sont acceptables, mais je préfère la version plus courte, et les valeurs 6 et 7 sont simplement absurdement verbales.
Cependant, vous voudrez peut-être suivre un autre chemin, en suivant le style “presque toujours automatique” de Herb Sutter :
auto arr8 = std::array{{1, 2, 3}};
Cette réponse lie un rapport de bogue dans lequel -Wmissing-braces
n’est plus activé par défaut lors de l’utilisation de -Wall
. Si vous activez -Wmissing-braces
, gcc
se plaindra de 0, 2, 4 et 6 (idem clang
).
L’élision des accolades est autorisée pour les instructions sous la forme T a = { ... }
mais pas T a { }
.
Pourquoi le comportement C ++ initializer_list pour std :: vector et std :: array est-il différent?
Voici la réponse de James McNellis :
Cependant, ces accolades supplémentaires ne peuvent être supprimées que “dans une déclaration de la forme T x = {a};” (C ++ 11 §8.5.1 / 11), c’est-à-dire lorsque l’ancien style = est utilisé. Cette règle autorisant la suppression d’accolades ne s’applique pas à l’initialisation directe de liste. Une note de bas de page se lit comme suit: “Les accolades ne peuvent pas être supprimées dans d’autres utilisations de l’initialisation de liste.”
Il existe un rapport de défaut concernant cette ressortingction: Défaut CWG # 1270. Si le projet de résolution est adopté, l’élision de support sera autorisée pour d’autres formes d’initialisation de liste, …
Si la résolution proposée est adoptée, la suppression par accolade sera autorisée pour les autres formes d’initialisation de liste, et les éléments suivants seront bien formés: std :: array y {1, 2, 3, 4};
Et la réponse de Xeo :
… tandis que std :: array n’a pas de constructeur et que la liste d’initialisation {1, 2, 3, 4} entre accolades n’est en fait pas interprétée comme une liste std :: initializer_list, mais une initialisation d’agrégat pour le tableau de style C de std :: array (d’où provient le deuxième ensemble d’accolades: un pour std :: array, un pour le tableau de membres de style C interne).
std::array
n’a pas de constructeur qui prend un initializer_list
. Pour cette raison, il est traité comme une initialisation globale. Si c’était le cas, cela ressemblerait à quelque chose comme ceci:
#include #include struct test { int inner[3]; test(std::initializer_list list) { std::copy(list.begin(), list.end(), inner); } }; #include int main() { test t{1, 2, 3}; test t2({1, 2, 3}); test t3 = {1, 2, 3}; test t4 = test({1, 2, 3}); for (int i = 0; i < 3; i++) std::cout << t.inner[i]; for (int i = 0; i < 3; i++) std::cout << t2.inner[i]; for (int i = 0; i < 3; i++) std::cout << t3.inner[i]; for (int i = 0; i < 3; i++) std::cout << t4.inner[i]; }
Les 2 derniers sont redondants: vous pouvez utiliser les 6 formes de déclaration de tableau situées à droite de l’affectation. De plus, si votre compilateur n’optimise pas la copie, ces versions sont moins efficaces.
Les doubles accolades sont obligatoires avec le constructeur initializer-list, votre troisième ligne n’est donc pas valide.