Агрегатная инициализация
Инициализирует агрегат из списка инициализаторов. Это форма инициализации списком. (начиная с C++11)
Содержание |
[править] Синтаксис
T объект = { арг1, арг2, ... };
|
(1) | ||||||||
T объект { арг1, арг2, ... };
|
(2) | (начиная с C++11) | |||||||
T объект = { . назн1 = арг1 , . назн2 { арг2 } ... };
|
(3) | (начиная с C++20) | |||||||
T объект { . назн1 = арг1 , . назн2 { арг2 } ... };
|
(4) | (начиная с C++20) | |||||||
[править] Объяснение
[править] Определения
Агрегат относится к одному из следующих типов:
- тип массива
- классовый тип (обычно struct или union), который имеет
|
(до C++11) |
|
(начиная с C++11) (до C++20) |
|
(начиная с C++20) |
- нет закрытых или защищённых прямых (начиная с C++17) нестатических элементов данных
|
(до C++17) |
|
(начиная с C++17) |
- нет виртуальных функций-элементов
(начиная с C++11) (до C++14) |
Элементами агрегата являются:
- для массива это элементы массива в порядке возрастания индекса или
|
(до C++17) |
|
(начиная с C++17) |
[править] Процесс
Эффекты агрегатной инициализации:
- количество предложений инициализатора в списке инициализаторов превышает количество элементов агрегата, или
- инициализируется массив неизвестной привязки с пустым списком инициализаторов ({}).
char cv[4] = {'a', 's', 'd', 'f', 0}; // ошибка int x[] = {} // ошибка
|
(начиная с C++20) |
- Иначе, если список инициализаторов не пуст, явно инициализированные элементы агрегата являются первыми n элементами агрегата, где n количество элементов в списке инициализаторов.
- Иначе список инициализаторов должен быть пустым ({}) и не иметь явно инициализированных элементов.
- Программа некорректа, если агрегат является объединением и имеется два или более явно инициализированных элемента:
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++11).
- Если предложение инициализатора представляет собой вложенный список инициализации в фигурных скобках (который не является выражением), список инициализирует соответствующий элемент из этого предложения, и (начиная с C++11) правило применяется рекурсивно, если соответствующий элемент является подагрегатом.
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
Для агрегата не объединения каждый элемент, который не является явно инициализированным элементом, инициализируется следующим образом:
|
(начиная с C++11) |
- Иначе, если элемент не является ссылкой, он инициализируется копированием из пустого списка инициализаторов.
- Иначе программа некорректна.
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"};
Если агрегат представляет собой объединение, а список инициализаторов пуст, то
|
(начиная с C++11) |
- Иначе первый элемент объединения (если есть) инициализируется копированием из пустого списка инициализаторов.
[править] Пропуск скобок
Фигурные скобки вокруг вложенных списков инициализаторов могут быть пропущены (опущены), и в этом случае для инициализации каждого элемента соответствующего подагрегата используется столько предложений инициализатора, сколько необходимо, а последующие предложения инициализатора используются для инициализации следующих элементов объект. Однако, если у объекта есть подагрегат без каких-либо элементов (пустая структура или структура, содержащая только статические элементы), исключение фигурных скобок не допускается, а необходимо использовать пустой вложенный список {}.
Назначенные инициализаторыСинтаксические формы (3,4) известны как назначенные инициализаторы: каждое назначение должно именовать прямой нестатический элемент данных T, и все назначения, используемые в выражение, должны стоять в том же порядке, что и элементы данных T. struct A { int x; int y; int z; }; A a{.y = 2, .x = 1}; // ошибка; порядок назначения не соответствует порядку объявления A b{.x = 1, .z = 2}; // ok, b.y инициализируется в 0 Каждый прямой нестатический элемент данных, именованный назначенным инициализатором, инициализируется из соответствующего инициализатора в скобках или равенства, которое следует за назначением. Сужающие преобразования запрещены. Назначенный инициализатор может использоваться для инициализации объединения в состояние, отличное от первого. Для объединения может быть предоставлен только один инициализатор. union u { int a; const char* b; }; u f = {.b = "asdf"}; // OK, активный элемент b объединения u g = {.a = 1, .b = "asdf"}; // Ошибка, может быть указан только один инициализатор Для агрегата не объединения элементы, для которых не указан назначенный инициализатор, инициализируются так же, как описано выше, когда количество предложений инициализатора меньше, чем количество элементов (инициализаторы элементов по умолчанию, если они предоставлены, в противном случае инициализация пустым списком): struct A { string str; int n = 42; int m = -1; }; A{.m = 21} // Инициализирует str с помощью {}, которое вызывает конструктор по умолчанию // затем инициализирует n с помощью = 42 // затем инициализирует m с помощью = 21 Если агрегат, который инициализируется с помощью назначенного предложения инициализатора, имеет элемент анонимного объединения, соответствующий назначенный инициализатор должен именовать один из элементов этого анонимного объединения. Примечание: назначенная инициализация не по порядку, вложенная назначенная инициализация, смешивание назначенных инициализаторов и обычных инициализаторов, а также назначенная инициализация массивов поддерживаются в языке программирования C, но не разрешены в C++. struct A { int x, y; }; struct B { struct A a; }; struct A a = {.y = 1, .x = 2}; // допустимо в C, не допусьтимо в C++ (не по порядку) int arr[3] = {[1] = 5}; // допустимо в C, не допусьтимо в C++ (массив) struct B b = {.a.x = 0}; // допустимо в C, не допусьтимо в C++ (вложение) struct A a = {.x = 1, 2}; // допустимо в C, не допусьтимо в C++ (смешение) |
(начиная с C++20) |
[править] Массивы символов
Массивы обычных типов символов (char, signed char, unsigned char), char8_t (начиная с C++20), char16_t, char32_t (начиная с C++11) или wchar_t можно инициализировать обычными строковыми литералами, UTF-8 строковыми литералами (начиная с C++20), UTF-16 строковыми литералами, UTF-32 строковыми литералами (начиная с C++11) или широкими строковыми литералами, соответственно, необязательно заключёнными в фигурные скобки. Кроме того, массив char или unsigned char может быть инициализирован строковым литералом UTF-8, необязательно заключённым в фигурные скобки. (начиная с 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 или при создании временного объекта.
В 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++20) | 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("один"), // инициализация копированием "два", // преобразование, затем инициализация // копированием {'т', 'р', 'и'}}; // инициализация списком union U { int a; const char* b; }; U u1 = {1}; // OK, первый элемент объединения // U u2 = {0, "asdf"}; // ошибка: слишком много инициализаторов для объединения // U u3 = {"asdf"}; // ошибка: неверное преобразование в int [](...) { std::puts("Выбрасываем неиспользуемые переменные... Готово."); } ( 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
Вывод:
Выбрасываем неиспользуемые переменные... Готово.
[править] Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
Номер | Применён | Поведение в стандарте | Корректное поведение |
---|---|---|---|
CWG 413 | C++98 | анонимные битовые поля инициализировались при агрегатной инициализации |
они игнорируются |
CWG 737 | C++98 | когда массив символов инициализируется строковым литералом, имеющим меньше символов, чем размер массива, символьные элементы после завершающего '\0' неинициализировались |
они инициализируются нулями |
CWG 1270 | C++11 | пропуск скобок разрешалось использовать только при инициализации копированием списка |
разрешено в другом месте |
CWG 1518 | C++11 | класс, который объявляет явный конструктор по умолчанию или имеет унаследованные конструкторы, должен быть агрегатом |
это не агрегат |
CWG 1622 | C++98 | объединение не может быть инициализировано {} | позволено |
CWG 2272 | C++98 | нестатический элемент ссылка, которая не была явно инициализирована, инициализировалась копированием из пустого списка инициализаторов |
в этом случае программа некорректна |
WG не указан | C++20 | строковый литерал UTF-8 не мог инициализировать массив char или unsigned char, что было несовместимо с C или C++17 |
такая инициализация позволена |
[править] Смотрите также
Документация C по Инициализация структуры и объединения
|