Aggregatinitialisierung
Initialisiert ein Aggregat aus einer Initialisierungsliste. Es ist eine Form der Listeninitialisierung(seit C++11).
Inhalt |
[bearbeiten] Syntax
T Objekt = { arg1, arg2, ... }; |
(1) | ||||||||
T Objekt { arg1, arg2, ... }; |
(2) | (seit C++11) | |||||||
T Objekt = { .des1 = arg1 , .des2 { arg2 } ... }; |
(3) | (seit C++20) | |||||||
T Objekt { .des1 = arg1 , .des2 { arg2 } ... }; |
(4) | (seit C++20) | |||||||
[bearbeiten] Definitionen
[bearbeiten] Aggregat
Ein Aggregat ist einer der folgenden Typen
- Arraytypen
- Klassentypen, die
|
(bis C++11) |
|
(seit C++11) (bis C++20) |
|
(seit C++20) |
- keine privaten oder geschützten direkten nicht-statischen Datenmember haben
|
(bis C++17) |
|
(seit C++17) |
- keine virtuellen Memberfunktionen haben
|
(seit C++11) (bis C++14) |
[bearbeiten] Element
Die Elemente eines Aggregats sind
- bei einem Array die Array-Elemente in aufsteigender Indexreihenfolge, oder
|
(bis C++17) |
|
(seit C++17) |
[bearbeiten] Zugehörigkeit
Jede Initialisierungsklausel in einer klammergefassten Initialisierungsliste wird gesagt, dass sie einem Element des zu initialisierenden Aggregats oder einem Element eines seiner Unteraggregate zugehört.
Betrachtet man die Sequenz der Initialisierungsklauseln und die Sequenz der zunächst gebildeten Aggregat-Elemente als die Sequenz der Elemente des zu initialisierenden Aggregats und wie unten beschrieben modifiziert werden kann
- Für jede Initialisierungsklausel, wenn eine der folgenden Bedingungen erfüllt ist, gehört sie zum entsprechenden Aggregat-Element elem
- elem ist kein Aggregat.
- Die Initialisierungsklausel beginnt mit {.
- Die Initialisierungsklausel ist ein Ausdruck, und eine implizite Konversionssequenz kann gebildet werden, die den Ausdruck in den Typ von elem konvertiert.
- elem ist ein Aggregat, das selbst keine Aggregat-Elemente hat.
- Andernfalls ist elem ein Aggregat und dieses Unteraggregat wird in der Liste der Aggregat-Elemente durch die Sequenz seiner eigenen Aggregat-Elemente ersetzt, und die Analyse der Zugehörigkeit wird mit dem ersten solchen Element und der gleichen Initialisierungsklausel fortgesetzt. Mit anderen Worten, diese Regeln gelten rekursiv für die Unteraggregate des Aggregats.
Die Analyse ist abgeschlossen, wenn alle Initialisierungsklauseln aufgebraucht sind. Wenn noch Initialisierungsklauseln übrig sind, die keinem Element des Aggregats oder einem seiner Unteraggregate angehören, ist das Programm fehlerhaft.
struct S1 { int a, b; }; struct S2 { S1 s, t; }; // Each subaggregate of “x” is appertained to an initializer clause starting with { S2 x[2] = { // appertains to “x[0]” { {1, 2}, // appertains to “x[0].s” {3, 4} // appertains to “x[0].t” }, // appertains to “x[1]” { {5, 6}, // appertains to “x[1].s” {7, 8} // appertains to “x[1].t” } }; // “x” and “y” have the same value (see below) S2 y[2] = {1, 2, 3, 4, 5, 6, 7, 8}; // The process of the appertainment analysis of “y”: // 1. Initializes the aggregate element sequence (x[0], x[1]) and // the initializer clause sequence (1, 2, 3, 4, 5, 6, 7, 8). // 2. Starting from the first elements of each sequence, // checks whether 1 appertains to x[0]: // · x[0] is an aggregate. // · 1 does not begin with {. // · 1 is an expression, but it cannot be implicitly converted to S2. // · x[0] has aggregate elements. // 3. 0 cannot appertain to x[0], therefore x[0] is replaced by x[0].s and x[0].t, // the aggregate element sequence becomes (x[0].s, x[0].t, x[1]). // 4. Resumes the appertainment check, but 1 cannot appertain to x[0].s either. // 5. The aggregate element sequence now becomes (x[0].s.a, x[0].s.b, x[0].t, x[1]). // 6. Resumes the appertainment check again: // 1 appertains to x[0].s.a, and 2 appertains to x[0].s.b. // 7. The rest of the appertainment analysis works similarly. char cv[4] = {'a', 's', 'd', 'f', 0}; // Error: too many initializer clauses
[bearbeiten] Initialisierungsprozess
[bearbeiten] Bestimmung der Elementart
Die Auswirkungen der Aggregatinitialisierung sind
|
(seit C++20) |
- Andernfalls, wenn die Initialisierungsliste nicht leer ist, sind die explizit initialisierten Elemente des Aggregats die Elemente mit einer zugeordneten Initialisierungsklausel und die Elemente mit einem Unteraggregat mit einer zugeordneten Initialisierungsklausel.
- Andernfalls muss die Initialisierungsliste leer sein ({}) und es gibt keine explizit initialisierten Elemente.
- Das Programm ist fehlerhaft, wenn das Aggregat eine Union ist und es zwei oder mehr explizit initialisierte Elemente gibt.
union u { int a; const char* b; }; u a = {1}; // OK: explicitly initializes member `a` u b = {0, "asdf"}; // error: explicitly initializes two members u c = {"asdf"}; // error: int cannot be initialized by "asdf" // C++20 designated initializer lists u d = {.b = "asdf"}; // OK: can explicitly initialize a non-initial member u e = {.a = 1, .b = "asdf"}; // error: explicitly initializes two members
[bearbeiten] Explizit initialisierte Elemente
Für jedes explizit initialisierte Element
struct C { union { int a; const char* p; }; int x; } c = {.a = 1, .x = 3}; // initializes c.a with 1 and c.x with 3
|
(seit C++20) |
|
(bis C++20) |
|
(seit C++20) |
- Wenn eine Initialisierungsklausel dem Aggregat-Element zugeordnet ist, dann wird das Aggregat-Element aus der Initialisierungsklausel kopierinitialisiert.
- Andernfalls wird das Aggregat-Element aus einer klammergefassten Initialisierungsliste kopierinitialisiert, die aus allen Initialisierungsklauseln besteht, die Unterobjekten des Aggregat-Elements zugeordnet sind, in der Reihenfolge ihres Erscheinens.
struct A { int x; struct B { int i; int j; } b; } a = {1, {2, 3}}; // initializes a.x with 1, a.b.i with 2, a.b.j with 3 struct base1 { int b1, b2 = 42; }; struct base2 { base2() { b3 = 42; } int b3; }; struct derived : base1, base2 { int d; }; derived d1{{1, 2}, {}, 4}; // initializes d1.b1 with 1, d1.b2 with 2, // d1.b3 with 42, d1.d with 4 derived d2{{}, {}, 4}; // initializes d2.b1 with 0, d2.b2 with 42, // d2.b3 with 42, d2.d with 4
[bearbeiten] Implizit initialisierte Elemente
Für ein nicht-Union-Aggregat wird jedes Element, das kein explizit initialisiertes Element ist, wie folgt initialisiert:
|
(seit C++11) |
- Andernfalls, wenn das Element keine Referenz ist, wird das Element aus einer leeren Initialisierungsliste kopierinitialisiert.
- Andernfalls ist das Programm fehlerhaft.
struct S { int a; const char* b; int c; int d = b[a]; }; // initializes ss.a with 1, // ss.b with "asdf", // ss.c with the value of an expression of the form int{} (that is, 0), // and ss.d with the value of ss.b[ss.a] (that is, 's') S ss = {1, "asdf"};
Wenn das Aggregat eine Union ist und die Initialisierungsliste leer ist, dann:
|
(seit C++11) |
- Andernfalls wird das erste Mitglied der Union (falls vorhanden) aus einer leeren Initialisierungsliste kopierinitialisiert.
[bearbeiten] Arrays mit unbekannter Größe
Die Anzahl der Elemente in einem Array mit unbekannter Größe, das mit einer klammergefassten Initialisierungsliste initialisiert wird, ist die Anzahl der explizit initialisierten Elemente des Arrays. Ein Array mit unbekannter Größe kann nicht mit {} initialisiert werden.
int x[] = {1, 3, 5}; // x has 3 elements struct Y { int i, j, k; }; Y y[] = {1, 2, 3, 4, 5, 6}; // y has only 2 elements: // 1, 2 and 3 appertain to y[0], // 4, 5 and 6 appertain to y[1] int z[] = {} // Error: cannot declare an array without any element
Bezeichner-InitialisiererDie Syntaxformen (3,4) werden als Bezeichnerinitialisierer bezeichnet: Jeder Bezeichner muss einen direkten nicht-statischen Datenmember von T benennen, und alle im Ausdruck verwendeten Bezeichner müssen in derselben Reihenfolge erscheinen wie die Datenmember von T. struct A { int x; int y; int z; }; A a{.y = 2, .x = 1}; // error; designator order does not match declaration order A b{.x = 1, .z = 2}; // ok, b.y initialized to 0 Jeder direkte nicht-statische Datenmember, der vom Bezeichnerinitialisierer benannt wird, wird aus dem entsprechenden klammer-oder-Gleichheits-Initialisierer, der dem Bezeichner folgt, initialisiert. Verengungskonvertierungen sind verboten. Bezeichnerinitialisierer können verwendet werden, um eine Union in einen anderen Zustand als den ersten zu initialisieren. Für eine Union darf nur ein Initialisierer bereitgestellt werden. union u { int a; const char* b; }; u f = {.b = "asdf"}; // OK, active member of the union is b u g = {.a = 1, .b = "asdf"}; // Error, only one initializer may be provided Für ein nicht-Union-Aggregat werden Elemente, für die kein Bezeichnerinitialisierer bereitgestellt wird, wie oben beschrieben für den Fall, dass die Anzahl der Initialisierungsklauseln geringer ist als die Anzahl der Member (Standard-Memberinitialisierer, falls vorhanden, ansonsten leere Listeninitialisierung) initialisiert. struct A { string str; int n = 42; int m = -1; }; A{.m = 21} // Initializes str with {}, which calls the default constructor // then initializes n with = 42 // then initializes m with = 21 Wenn das mit einer Bezeichnerinitialisierungsklausel initialisierte Aggregat ein anonymes Union-Mitglied hat, muss der entsprechende Bezeichner ein Mitglied dieser anonymen Union benennen. Hinweis: Nicht sortierte Bezeichnerinitialisierung, verschachtelte Bezeichnerinitialisierung, Vermischung von Bezeichnerinitialisierern und regulären Initialisierern sowie Bezeichnerinitialisierung von Arrays werden in der C-Programmiersprache unterstützt, sind aber in C++ nicht erlaubt. struct A { int x, y; }; struct B { struct A a; }; struct A a = {.y = 1, .x = 2}; // valid C, invalid C++ (out of order) int arr[3] = {[1] = 5}; // valid C, invalid C++ (array) struct B b = {.a.x = 0}; // valid C, invalid C++ (nested) struct A a = {.x = 1, 2}; // valid C, invalid C++ (mixed) |
(seit C++20) |
[bearbeiten] Zeichen-Arrays
Arrays von gewöhnlichen Zeichentypen (char, signed char, unsigned char), char8_t(seit C++20), char16_t, char32_t(seit C++11), oder wchar_t können aus gewöhnlichen String-Literalen, UTF-8-String-Literalen(seit C++20), UTF-16-String-Literalen, UTF-32-String-Literalen(seit C++11), oder breiten String-Literalen, jeweils optional in Klammern eingeschlossen, initialisiert werden. Zusätzlich kann ein Array aus char oder unsigned char durch ein UTF-8-String-Literal, optional in Klammern eingeschlossen, initialisiert werden(seit C++20). Sukzessive Zeichen des String-Literals (einschließlich des impliziten abschließenden Nullzeichens) initialisieren die Elemente des Arrays, mit einer Ganzzahlkonvertierung, falls für Wert der Quelle und des Ziels erforderlich(seit C++20). Wenn die Größe des Arrays angegeben ist und größer ist als die Anzahl der Zeichen im String-Literal, werden die verbleibenden Zeichen Null-initialisiert.
char a[] = "abc"; // equivalent to char a[4] = {'a', 'b', 'c', '\0'}; // unsigned char b[3] = "abc"; // Error: initializer string too long unsigned char b[5]{"abc"}; // equivalent to unsigned char b[5] = {'a', 'b', 'c', '\0', '\0'}; wchar_t c[] = {L"кошка"}; // optional braces // equivalent to wchar_t c[6] = {L'к', L'о', L'ш', L'к', L'а', L'\0'};
[bearbeiten] Hinweise
Eine Aggregatklasse oder ein Aggregat-Array kann Nicht-Aggregat- öffentliche Basen(seit C++17), Member oder Elemente enthalten, die wie oben beschrieben initialisiert werden (z. B. Kopierinitialisierung aus der entsprechenden Initialisierungsklausel).
Bis C++11 waren Verengungskonvertierungen bei der Aggregatinitialisierung zulässig, aber sie sind nicht mehr erlaubt.
Bis C++11 konnte die Aggregatinitialisierung nur bei der Variablendefinition verwendet werden und konnte aufgrund von Syntaxbeschränkungen nicht in einer Konstruktor-Initialisierungsliste, einem new-Ausdruck oder der Erstellung temporärer Objekte verwendet werden.
In C kann ein Zeichen-Array der Größe eins kleiner als die Größe des String-Literals aus einem String-Literal initialisiert werden; das resultierende Array ist nicht null-terminiert. Dies ist in C++ nicht erlaubt.
| Feature-Testmakro | Wert | Std | Feature |
|---|---|---|---|
__cpp_aggregate_bases |
201603L |
(C++17) | Aggregatklassen mit Basisklassen |
__cpp_aggregate_nsdmi |
201304L |
(C++14) | Aggregatklassen mit Standard-Memberinitialisierern |
__cpp_aggregate_paren_init |
201902L |
(C++20) | Aggregatinitialisierung in Form der Direktinitialisierung |
__cpp_char8_t |
202207L |
(C++23) (DR20) |
char8_t Kompatibilitäts- und Portabilitätsfix (Initialisierung von (unsigned char-Arrays) mit UTF-8-String-Literalen erlauben) |
__cpp_designated_initializers |
201707L |
(C++20) | Bezeichner-Initialisierer |
[bearbeiten] Beispiel
#include <array> #include <cstdio> #include <string> struct S { int x; struct Foo { int i; int j; int a[3]; } b; }; int main() { S s1 = {1, {2, 3, {4, 5, 6}}}; S s2 = {1, 2, 3, 4, 5, 6}; // same, but with brace elision S s3{1, {2, 3, {4, 5, 6}}}; // same, using direct-list-initialization syntax S s4{1, 2, 3, 4, 5, 6}; // error until CWG 1270: // brace elision only allowed with equals sign int ar[] = {1, 2, 3}; // ar is int[3] // char cr[3] = {'a', 'b', 'c', 'd'}; // too many initializer clauses char cr[3] = {'a'}; // array initialized as {'a', '\0', '\0'} int ar2d1[2][2] = {{1, 2}, {3, 4}}; // fully-braced 2D array: {1, 2} // {3, 4} int ar2d2[2][2] = {1, 2, 3, 4}; // brace elision: {1, 2} // {3, 4} int ar2d3[2][2] = {{1}, {2}}; // only first column: {1, 0} // {2, 0} std::array<int, 3> std_ar2{{1, 2, 3}}; // std::array is an aggregate std::array<int, 3> std_ar1 = {1, 2, 3}; // brace-elision okay // int ai[] = {1, 2.0}; // narrowing conversion from double to int: // error in C++11, okay in C++03 std::string ars[] = {std::string("one"), // copy-initialization "two", // conversion, then copy-initialization {'t', 'h', 'r', 'e', 'e'}}; // list-initialization union U { int a; const char* b; }; U u1 = {1}; // OK, first member of the union // U u2 = {0, "asdf"}; // error: too many initializers for union // U u3 = {"asdf"}; // error: invalid conversion to int [](...) { std::puts("Garbage collecting unused variables... Done."); } ( s1, s2, s3, s4, ar, cr, ar2d1, ar2d2, ar2d3, std_ar2, std_ar1, u1 ); } // aggregate struct base1 { int b1, b2 = 42; }; // non-aggregate struct base2 { base2() : b3(42) {} int b3; }; // aggregate in C++17 struct derived : base1, base2 { int d; }; derived d1{{1, 2}, {}, 4}; // d1.b1 = 1, d1.b2 = 2, d1.b3 = 42, d1.d = 4 derived d2{{}, {}, 4}; // d2.b1 = 0, d2.b2 = 42, d2.b3 = 42, d2.d = 4
Ausgabe
Garbage collecting unused variables... Done.
[bearbeiten] Fehlerberichte
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 413 | C++98 | anonyme Bitfelder wurden in der Aggregatinitialisierung initialisiert | sie werden ignoriert |
| CWG 737 | C++98 | wenn ein Zeichen-Array mit einem String-Literal initialisiert wird mit weniger Zeichen als die Array-Größe, die Zeichen Elemente nach dem abschließenden '\0' waren nicht initialisiert |
sie sind Null-initialisiert |
| CWG 1270 | C++11 | Klammerauslassung durfte nur in der Kopier-Listeninitialisierung verwendet werden | woanders erlaubt |
| CWG 1518 | C++11 | eine Klasse, die einen expliziten Standardkonstruktor deklariert oder vererbte Konstruktoren hat, könnte ein Aggregat sein |
es ist kein Aggregat |
| CWG 1622 | C++98 | eine Union konnte nicht mit {} initialisiert werden | erlaubt |
| CWG 2149 (P3106R1) |
C++98 | Es war unklar, ob die Klammerauslassung anwendbar ist bei der Array-Größenermittlung |
anwendbar |
| CWG 2272 | C++98 | ein nicht-statisches Referenzmitglied, das nicht explizit initialisiert wurde, wurde aus einer leeren Initialisierungsliste kopierinitialisiert |
das Programm ist schlecht ge- formt in diesem Fall |
| CWG 2610 | C++17 | Aggregattypen konnten keine privaten oder geschützten indirekten Basisklassen haben | erlaubt |
| CWG 2619 | C++20 | Die Art der Initialisierung aus Bezeichnerinitialisierern war unklar | es hängt von der Art des Initialisierers ab |
| P2513R4 | C++20 | Ein UTF-8-String-Literal konnte kein Array von char initialisieren oder unsigned char, was inkompatibel mit C oder C++17 war |
solche Initialisierung ist gültig |
[bearbeiten] Siehe auch
| C-Dokumentation für Initialisierung von Strukturen und Unions
|