Инициализация агрегатов
Инициализирует агрегат из списка инициализаторов. Это форма инициализации списком.(с 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) |
Объяснение
Определения
Агрегат — это один из следующих типов:
- тип массива
- тип класса (обычно,
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}; // error
int x[] = {} // error
| (с C++20) |
- В противном случае, если список инициализаторов не пустой, явно инициализированные элементы агрегата — это первые n элементов агрегата, где n — количество элементов в списке инициализаторов.
- В противном случае, список инициализаторов должен быть пустым (
{}), и явно инициализированных элементов нет.
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Инициализация элементов
Для каждого явно инициализированного элемента:
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 | (с C++20) |
| (до C++20) |
| (с C++20) |
- Если инициализатор — это выражение, неявные преобразования разрешены в соответствии с копирующей инициализацией, за исключением запрета сужающих преобразований(с C++11).
- Если инициализатор — вложенный braced-init-list (который не является выражением), то соответствующий элемент инициализируется списком(с C++11) из этого инициализатора, что рекурсивно применяется, если соответствующий элемент является под-агрегатом.
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
Для агрегата, не являющегося объединением, каждый элемент, который не является явно инициализированным элементом, инициализируется следующим образом:
| (с C++11) |
- В противном случае, если элемент не является ссылкой, элемент инициализируется по копии из пустого списка инициализаторов.
- В противном случае, программа некорректна.
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"};
Если агрегат является объединением, и список инициализаторов пустой, то
| (с C++11) |
- В противном случае, первый член объединения (если есть) инициализируется по копии из пустого списка инициализаторов.
Опускание фигурных скобок
Скобки вокруг вложенных списков инициализации можно опустить, в этом случае для инициализации каждого члена или элемента соответствующего подсовокупности используется необходимое количество инициализирующих предложений, а последующие инициализирующие предложения используются для инициализации последующих членов объекта. Однако, если объект имеет подсовокупность без каких-либо членов (пустой структуру или структуру, содержащую только статические члены), опущение скобок не разрешено, и необходимо использовать пустой вложенный список {}.
Обозначенные инициализаторыСинтаксические формы (3,4) известны как обозначенные инициализаторы: каждый означающий должен обозначить непосредственный нестатический член данных T, и все означающие используемые в выражении должны появляться в том же порядке, что и члены данных 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Каждый непосредственный нестатический член данных, обозначенный обозначенным инициализатором, инициализируется из соответствующего инициализатора в фигурных скобках или с помощью оператора присваивания, который следует за обозначением. Ограничения по сужению типов запрещены. Обозначенный инициализатор может быть использован для инициализации объединения в состояние, отличное от первого. Для объединения может быть предоставлен только один инициализатор. 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Для не-объединения элементы, для которых не предоставлен обозначенный инициализатор, инициализируются так же, как описано выше, когда количество инициализирующих предложений меньше числа членов (используются значения по умолчанию для членов, если они предоставлены, иначе пустая инициализация): 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Если агрегат, который инициализируется с помощью обозначенного инициализатора, имеет анонимный член объединения, соответствующий обозначенный инициализатор должен обозначать один из членов этого анонимного объединения. Примечание: инициализация с обозначением в неверном порядке, вложенная инициализация с обозначением, смешение обозначенных инициализаторов и обычных инициализаторов, и обозначенная инициализация массивов поддерживаются в языке программирования 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(с 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) Последовательные символы строкового литерала (включая неявный завершающий нулевой символ) инициализируют элементы массива, с использованием неявного преобразования при необходимости для исходного и целевого значения(с C++20). Если размер массива указан и он больше числа символов в строковом литерале, оставшиеся символы инициализируются нулями.
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'};Примечания
Класс агрегата или массив может включать неагрегатные публичные базовые классы(с C++17), члены или элементы, которые инициализируются, как описано выше (например, копированием из соответствующего инициализирующего предложения).
До C++11 преобразования сужения разрешались при агрегатной инициализации, но теперь они не допускаются.
До C++11 агрегатная инициализация могла использоваться только при определении переменной и не могла использоваться в списке инициализаторов конструктора, операторе new или при создании временных объектов из-за ограничений синтаксиса.
В 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++20) (DR) |
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}; // 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 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Вывод:
Garbage unused variables... Done.
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам 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 | нестатический ссылочный член, который не был явно инициализирован, был копирован из пустого списка инициализации | программа в этом случае некорректна |
| CWG 2610 | C++17 | типы агрегатов не могли иметь базовые классы, объявленные как private или protected | разрешено |
| CWG 2619 | C++20 | тип инициализации из обозначенных инициализаторов был неясным | зависит от типа инициализатора |
| P2513R4 | C++20 | UTF-8 строковый литерал не мог инициализировать массив charили unsigned char, что было несовместимо с C или C++17 | такая инициализация допустима |
См. также
| C документация для Инициализация структур и объединений |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/aggregate_initialization