Агрегатная инициализация
Инициализирует агрегат списком инициализаторов. Это форма списковой инициализации(начиная с 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) |
Элемент
Элементы агрегата — это:
- для массива — элементы массива в порядке возрастания индексов либо
| (до C++17) |
| (начиная с C++17) |
Соответствие элементу
Каждая инициализирующая часть в списке инициализаторов, заключённом в фигурные скобки, считается относящейся к элементу инициализируемого агрегата или к элементу одного из его подагрегатов.
Рассмотрим последовательность инициализирующих частей и последовательность элементов агрегата, изначально сформированную как последовательность элементов инициализируемого агрегата и потенциально изменяемую, как описано ниже:
- Для каждой инициализирующей части, если выполняется любое из следующих условий, она относится к соответствующему элементу агрегата
elem: -
elemне является агрегатом. - Инициализирующая часть начинается с
{. - Инициализирующая часть является выражением, и может быть сформирована последовательность неявного преобразования, преобразующая это выражение к типу
elem. -
elemявляется агрегатом, который сам не имеет элементов агрегата. - В противном случае
elemявляется агрегатом, и этот подагрегат заменяется в списке элементов агрегата последовательностью собственных элементов агрегата; анализ соответствия элементам продолжается с первым таким элементом и той же инициализирующей частью. Иными словами, эти правила рекурсивно применяются к подагрегатам агрегата.
Анализ завершается, когда исчерпаны все инициализирующие части. Если остаётся инициализирующая часть, не относящаяся ни к элементу агрегата, ни к элементу одного из его подагрегатов, программа некорректна.
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
Процесс инициализации
Определение вида элемента
Эффекты агрегатной инициализации:
| (начиная с C++20) |
- В противном случае, если список инициализаторов не пуст, явно инициализированными элементами агрегата являются элементы, которым соответствует инициализирующая часть, и элементы, имеющие подагрегат с соответствующей инициализирующей частью.
- В противном случае список инициализаторов должен быть пустым (
{}), и явно инициализированных элементов нет.
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) |
- Если инициализирующая часть относится к элементу агрегата, элемент агрегата копирующе инициализируется из этой инициализирующей части.
- В противном случае элемент агрегата копирующе инициализируется списком инициализаторов в фигурных скобках, состоящим из всех инициализирующих частей, относящихся к подобъектам этого элемента агрегата, в порядке их появления.
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) |
- В противном случае первый член объединения (если он есть) копирующе инициализируется пустым списком инициализаторов.
Массивы с неизвестными границами
Количество элементов массива с неизвестной границей, инициализируемого списком инициализаторов в фигурных скобках, равно количеству явно инициализированных элементов массива. Массив с неизвестной границей нельзя инициализировать с помощью {}.
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
Обозначенные инициализаторыСинтаксические формы (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, signedchar, unsignedchar), char8_t(начиная с C++20), char16_t, char32_t(начиная с C++11), или wchar_t могут инициализироваться обычными строковыми литералами, строковыми литералами UTF-8(начиная с C++20), строковыми литералами UTF-16 и UTF-32(начиная с C++11) или широкими строковыми литералами соответственно, при необходимости заключёнными в фигурные скобки. Кроме того, массив char или unsignedchar может инициализироваться строковым литералом 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++23) (DR20) | Совместимость и переносимость char8_t (разрешение инициализации массивов unsignedchar из строковых литералов 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 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
Вывод:
Garbage collecting unused variables... Done.
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были применены задним числом к ранее опубликованным стандартам C++.
| DR | Применён к | Поведение согласно опубликованной версии | Корректное поведение |
|---|---|---|---|
| CWG 413 | C++98 | анонимные битовые поля инициализировались при агрегатной инициализации | они игнорируются |
| CWG 737 | C++98 | если массив символов инициализировался строковым литералом, содержащим меньше символов, чем размер массива, символьные элементы после завершающего '\0' оставались неинициализированными | они инициализируются нулём |
| CWG 1270 | C++11 | опускать фигурные скобки разрешалось только при копирующей списковой инициализации | разрешено и в других случаях |
| CWG 1518 | C++11 | класс, объявляющий явный конструктор по умолчанию или имеющий унаследованные конструкторы, мог быть агрегатом | он не является агрегатом |
| CWG 1622 | C++98 | объединение нельзя было инициализировать с помощью {} | разрешено |
|
CWG 2149 (P3106R1) | C++98 | не было ясно, применимо ли опускание фигурных скобок при выводе размера массива | применимо |
| CWG 2272 | C++98 | нестатический член-ссылка, который явно не инициализирован, копирующе инициализировался пустым списком инициализаторов | в этом случае программа некорректна |
| CWG 2610 | C++17 | агрегатные типы не могли иметь закрытые или защищённые косвенные базовые классы | разрешено |
| CWG 2619 | C++20 | вид инициализации обозначенными инициализаторами был неясен | он зависит от вида инициализатора |
| P2513R4 | C++20 | строковый литерал UTF-8 не мог инициализировать массив char или unsignedchar, что было несовместимо с C и C++17 | такая инициализация допустима |
См. также
| Документация по C для Инициализация структур и объединений |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/aggregate_initialization