Aggregate initialization
Инициализирует агрегат из списка инициализации . Это форма list-initialization (since C++11) .
Содержание |
Синтаксис
T object
= {
arg1, arg2, ...
};
|
(1) | ||||||||
T object
{
arg1, arg2, ...
};
|
(2) | (начиная с C++11) | |||||||
T object
= { .
des1
=
arg1
, .
des2
{
arg2
}
...
};
|
(3) | (начиная с C++20) | |||||||
T object
{ .
des1
=
arg1
, .
des2
{
arg2
}
...
};
|
(4) | (начиная с C++20) | |||||||
Определения
Агрегат
Агрегат — это один из следующих типов:
- типы массивов
- классовые типы, которые имеют
|
(до C++11) |
|
(начиная с C++11)
(до C++20) |
|
(начиная с C++20) |
-
- нет приватных или защищённых прямых нестатических членов данных
|
(до C++17) |
|
(начиная с C++17) |
-
- нет виртуальных функций-членов
|
(начиная с C++11)
(до C++14) |
Элемент
Элементы aggregate это:
- для массива - элементы массива в порядке возрастания индексов, или
|
(до C++17) |
|
(начиная с C++17) |
Относящееся к делу
Каждый инициализатор в списке инициализации в фигурных скобках относится к элементу инициализируемого агрегата или к элементу одного из его подагрегатов.
Учитывая последовательность инициализаторов и последовательность элементов агрегата, изначально сформированную как последовательность элементов инициализируемого агрегата и потенциально изменяемую, как описано ниже:
- Для каждого инициализирующего выражения, если выполняется любое из следующих условий, оно относится к соответствующему агрегатному элементу elem :
-
- elem не является агрегатом.
- Начальное предложение начинается с { .
- Начальное предложение является выражением, и может быть сформирована последовательность неявных преобразований , преобразующая выражение в тип elem .
- elem является агрегатом, который сам не содержит агрегатных элементов.
- В противном случае, elem является агрегатом, и этот подагрегат заменяется в списке элементов агрегата последовательностью его собственных агрегатных элементов, а анализ принадлежности возобновляется с первого такого элемента и того же инициализирующего предложения. Другими словами, эти правила применяются рекурсивно к подагрегатам агрегата.
Анализ завершается, когда все инициализирующие выражения исчерпаны. Если остаётся любое инициализирующее выражение, которое не относится к элементу агрегата или одному из его подагрегатов, программа является некорректной.
struct S1 { long a, b; }; struct S2 { S1 s, t; }; // Каждый подагрегат "x" относится к инициализирующему выражению, начинающемуся с { S2 x[2] = { // относится к "x[0]" { {1L, 2L}, // относится к "x[0].s" {3L, 4L} // относится к "x[0].t" }, // относится к "x[1]" { {5L, 6L}, // относится к "x[1].s" {7L, 8L} // относится к "x[1].t" } }; // "x" и "y" имеют одинаковое значение (см. ниже) S2 y[2] = {1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L}; // Процесс анализа отношения для "y": // 1. Инициализирует последовательность элементов агрегата (x[0], x[1]) и // последовательность инициализирующих выражений (1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L). // 2. Начиная с первых элементов каждой последовательности, // проверяет, относится ли 1L к x[0]: // · x[0] является агрегатом. // · 1L не начинается с {. // · 1L является выражением, но не может быть неявно преобразовано в S2. // · x[0] имеет агрегатные элементы. // 3. 1L не может относиться к x[0], поэтому x[0] заменяется на x[0].s и x[0].t, // последовательность элементов агрегата становится (x[0].s, x[0].t, x[1]). // 4. Возобновляет проверку отношения, но 1L также не может относиться к x[0].s. // 5. Последовательность элементов агрегата теперь становится (x[0].s.a, x[0].s.b, x[0].t, x[1]). // 6. Снова возобновляет проверку отношения: // 1L относится к x[0].s.a, а 2L относится к x[0].s.b. // 7. Остальная часть анализа отношения работает аналогично. char cv[4] = {'a', 's', 'd', 'f', 0}; // Ошибка: слишком много инициализирующих выражений
Процесс инициализации
Определение типа элемента
Эффекты агрегатной инициализации:
|
(since C++20) |
-
- В противном случае, (начиная с C++20) если список инициализации не пуст, явно инициализированными элементами агрегата являются элементы с соответствующим предложением инициализации и элементы, содержащие подагрегат с соответствующим предложением инициализации.
- В противном случае, список инициализации должен быть пустым ( { } ), и явно инициализированных элементов нет.
- Программа является некорректной, если агрегат является объединением и имеется два или более явно инициализированных элемента:
union u { int a; const char* b; }; u a = {1}; // OK: явно инициализирует член `a` u b = {0, "asdf"}; // ошибка: явно инициализирует два члена u c = {"asdf"}; // ошибка: int не может быть инициализирован "asdf" // C++20 списки инициализации с указанием членов u d = {.b = "asdf"}; // OK: можно явно инициализировать не первый член u e = {.a = 1, .b = "asdf"}; // ошибка: явно инициализирует два члена
Явно инициализированные элементы
Для каждого явно инициализированного элемента:
struct C { union { int a; const char* p; }; int x; } c = {.a = 1, .x = 3}; // инициализирует c.a значением 1 и c.x значением 3
|
(начиная с C++20) |
|
(до C++20) |
|
(начиная с C++20) |
-
- Если инициализатор относится к элементу агрегата, то элемент агрегата copy-initialized из этого инициализатора.
- В противном случае, элемент агрегата copy-initialized из списка инициализации в фигурных скобках, состоящего из всех инициализаторов, относящихся к подобъектам элемента агрегата, в порядке их появления.
struct A { int x; struct B { int i; int j; } b; } a = {1, {2, 3}}; // инициализирует a.x значением 1, a.b.i значением 2, a.b.j значением 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}; // инициализирует 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
Неявно инициализированные элементы
Для необъединенного агрегата каждый элемент, который не является явно инициализированным элементом, инициализируется следующим образом:
|
(since C++11) |
- В противном случае, если элемент не является ссылкой, элемент copy-initialized из пустого списка инициализации.
- В противном случае программа является некорректной.
struct S { int a; const char* b; int c; int d = b[a]; }; // инициализирует ss.a значением 1, // ss.b значением "asdf", // ss.c значением выражения вида int{} (то есть 0), // и ss.d значением ss.b[ss.a] (то есть 's') S ss = {1, "asdf"};
Если агрегат является объединением и список инициализации пуст, то
|
(since C++11) |
- В противном случае, первый член объединения (если он существует) копически инициализируется из пустого списка инициализации.
Массивы с неизвестными границами
Количество элементов в массиве неизвестной границы, инициализированном списком инициализации в фигурных скобках, равно количеству явно инициализированных элементов массива. Массив неизвестной границы не может быть инициализирован с помощью { } .
int x[] = {1, 3, 5}; // x содержит 3 элемента struct Y { int i, j, k; }; Y y[] = {1, 2, 3, 4, 5, 6}; // y содержит только 2 элемента: // 1, 2 и 3 относятся к y[0], // 4, 5 и 6 относятся к y[1] int z[] = {} // Ошибка: невозможно объявить массив без элементов
Назначенные инициализаторыСинтаксические формы (3,4) известны как назначенные инициализаторы: каждый designator должен называть прямое нестатическое поле данных T, и все designator ы, используемые в выражении, должны появляться в том же порядке, что и поля данных T. struct A { int x; int y; int z; }; A a{.x = 1, .y = 2, .z = 3}; // ok A b{.y = 2, .z = 3, .x = 1}; // error; designator order does not match declaration order Каждое прямое нестатическое поле данных, указанное назначенным инициализатором, инициализируется из соответствующего фигурно-скобочного или равно-инициализатора, следующего за designator. Сужения типов запрещены. Назначенный инициализатор может использоваться для инициализации union в состояние, отличное от первого. Для union может быть предоставлен только один инициализатор. 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 Для не-union агрегата элементы, для которых не предоставлен назначенный инициализатор, инициализируются так же, как описано выше для случая, когда количество инициализаторов меньше количества членов (инициализаторы членов по умолчанию, где предоставлены, иначе инициализация пустым списком): 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 struct A { int x; int y; int z; }; A a{.x = 1, .z = 2}; // ok, b.y initialized to 0 A b{.y = 2, .x = 1}; // error; designator order does not match declaration order A c{.y = 2}; // ok, c.x and c.z are initialized to 0 constexpr A d{.z = 2}; // can be used with constexpr, as opposed to: constexpr A d; static_assert(d.x == 0 && d.y == 0); // d.x and d.y are initialized to 0 Если агрегат, инициализируемый с помощью назначенного инициализатора, имеет анонимный union член, соответствующий назначенный инициализатор должен называть один из членов этого анонимного union. Примечание: назначенная инициализация в неправильном порядке, вложенная назначенная инициализация, смешивание назначенных инициализаторов с обычными инициализаторами и назначенная инициализация массивов поддерживаются в языке программирования C , но не разрешены в C++. 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) |
(начиная с C++20) |
Массивы символов
Массивы обычных символьных типов ( char , signed char , unsigned char ) , char8_t (since C++20) , char16_t , char32_t (since C++11) , или wchar_t могут быть инициализированы из обычных строковых литералов , UTF-8 строковых литералов (since C++20) , UTF-16 строковых литералов, UTF-32 строковых литералов (since C++11) , или широких строковых литералов соответственно, опционально заключённых в фигурные скобки . Дополнительно, массив char или unsigned char может быть инициализирован UTF-8 строковым литералом, опционально заключённым в фигурные скобки (since C++20) . Последовательные символы строкового литерала (включая неявный завершающий нулевой символ) инициализируют элементы массива , с целочисленным преобразованием при необходимости для исходного и целевого значения (since C++20) . Если размер массива указан и он больше количества символов в строковом литерале, оставшиеся символы инициализируются нулями.
char a[] = "abc"; // эквивалентно char a[4] = {'a', 'b', 'c', '\0'}; // unsigned char b[3] = "abc"; // Ошибка: исходная строка слишком длинная unsigned char b[5]{"abc"}; // эквивалентно unsigned char b[5] = {'a', 'b', 'c', '\0', '\0'}; wchar_t c[] = {L"кошка"}; // необязательные фигурные скобки // эквивалентно wchar_t c[6] = {L'к', L'о', L'ш', L'к', L'а', L'\0'};
Примечания
Агрегатный класс или массив может включать неагрегатные публичные базовые классы (начиная с C++17) , члены или элементы, которые инициализируются, как описано выше (например, копирующая инициализация из соответствующего инициализирующего выражения).
До C++11 сужающие преобразования были разрешены при агрегатной инициализации, но теперь они запрещены.
До C++11 агрегатная инициализация могла использоваться только в определении переменных и не могла применяться в списке инициализации конструктора , в new-expression или при создании временных объектов из-за синтаксических ограничений.
В C символьный массив размером на единицу меньше размера строкового литерала может быть инициализирован из строкового литерала; результирующий массив не завершается нулевым символом. В C++ это не допускается.
| Макрос тестирования возможностей | Значение | Стандарт | Возможность |
|---|---|---|---|
__cpp_aggregate_bases
|
201603L
|
(C++17) | Агрегатные классы с базовыми классами |
__cpp_aggregate_nsdmi
|
201304L
|
(C++14) | Агрегатные классы с инициализаторами членов по умолчанию |
__cpp_aggregate_paren_init
|
201902L
|
(C++20) | Агрегатная инициализация в форме прямой инициализации |
__cpp_char8_t
|
202207L
|
(C++23)
(DR20) |
char8_t исправление совместимости и переносимости ( разрешение инициализации массивов ( unsigned char из строковых литералов UTF-8 ) |
__cpp_designated_initializers
|
201707L
|
(C++20) | Назначенные инициализаторы |
Пример
#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}; // то же, но с опусканием скобок S s3{1, {2, 3, {4, 5, 6}}}; // то же, используя синтаксис прямой списковой инициализации S s4{1, 2, 3, 4, 5, 6}; // ошибка до CWG 1270: // опускание скобок разрешено только со знаком равенства int ar[] = {1, 2, 3}; // ar имеет тип int[3] // char cr[3] = {'a', 'b', 'c', 'd'}; // слишком много инициализаторов char cr[3] = {'a'}; // массив инициализирован как {'a', '\0', '\0'} int ar2d1[2][2] = {{1, 2}, {3, 4}}; // полностью скобочный 2D массив: {1, 2} // {3, 4} int ar2d2[2][2] = {1, 2, 3, 4}; // опускание скобок: {1, 2} // {3, 4} int ar2d3[2][2] = {{1}, {2}}; // только первый столбец: {1, 0} // {2, 0} std::array<int, 3> std_ar2{{1, 2, 3}}; // std::array является агрегатом std::array<int, 3> std_ar1 = {1, 2, 3}; // опускание скобок допустимо // int ai[] = {1, 2.0}; // сужающее преобразование из double в int: // ошибка в C++11, допустимо в C++03 std::string ars[] = {std::string("one"), // копирующая инициализация "two", // преобразование, затем копирующая инициализация {'t', 'h', 'r', 'e', 'e'}}; // списковая инициализация union U { int a; const char* b; }; U u1 = {1}; // OK, первый член объединения // U u2 = {0, "asdf"}; // ошибка: слишком много инициализаторов для union // U u3 = {"asdf"}; // ошибка: недопустимое преобразование к int [](...) { std::puts("Garbage collecting unused variables... Done."); } ( s1, s2, s3, s4, ar, cr, ar2d1, ar2d2, ar2d3, std_ar2, std_ar1, u1 ); } // агрегат struct base1 { int b1, b2 = 42; }; // неагрегат struct base2 { base2() : b3(42) {} int b3; }; // агрегат в 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
Вывод:
Garbage collecting unused variables... Done.
Отчеты о дефектах
Следующие отчеты об изменениях в поведении, являющиеся дефектными, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применяется к | Поведение в опубликованной версии | Корректное поведение |
|---|---|---|---|
| CWG 413 | C++98 | анонимные битовые поля инициализировались при агрегатной инициализации | они игнорируются |
| CWG 737 | C++98 |
когда символьный массив инициализируется строковым литералом
с меньшим количеством символов, чем размер массива, символьные элементы после завершающего ' \0 ' оставались неинициализированными |
они
инициализируются нулями |
| CWG 1270 | C++11 | опускание фигурных скобок разрешалось только при copy-list-initialization | разрешено и в других случаях |
| CWG 1518 | C++11 |
класс, который объявляет явный конструктор по умолчанию или
имеет унаследованные конструкторы, мог быть агрегатом |
он не является
агрегатом |
| CWG 1622 | C++98 | union не мог быть инициализирован с помощью { } | разрешено |
|
CWG 2149
( P3106R1 ) |
C++98 |
было неясно, применимо ли опускание фигурных скобок
при выводе размера массива |
применимо |
| CWG 2272 | C++98 |
нестатический член-ссылка, который не был явно
инициализирован, copy-инициализировался из пустого списка инициализации |
программа является
некорректной в этом случае |
| CWG 2610 | C++17 | агрегатные типы не могли иметь приватные или защищенные косвенные базовые классы | разрешено |
| CWG 2619 | C++20 | тип инициализации из designated initializers был неясен |
зависит от
типа инициализатора |
| P2513R4 | C++20 |
UTF-8 строковый литерал не мог инициализировать массив
char
или unsigned char , что было несовместимо с C или C++17 |
такая инициализация
является допустимой |
Смотрите также
|
C documentation
для
Struct and union initialization
|