Spec-Zone.ru › C++

Инициализация агрегатов

Инициализирует агрегат из списка инициализаторов. Это форма инициализации списком.(с 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)
1,2) Инициализация агрегата обычным списком инициализаторов.
3,4) Инициализация агрегата с помощью назначенных инициализаторов (только для классов-агрегатов).

Объяснение

Определения

Агрегат — это один из следующих типов:

  • тип массива
  • тип класса (обычно, struct или union), у которого
  • нет пользовательских конструкторов
(до C++11)
  • нет пользовательских, унаследованных или явных конструкторов
(с C++11)
(до C++20)
  • нет пользовательских или унаследованных конструкторов
(с C++20)
  • нет закрытых или защищённых прямых(с C++17) нестатических данных-членов
  • нет базовых классов
(до C++17)
  • нет виртуальных базовых классов
  • нет закрытых или защищенных прямых базовых классов
(с C++17)
  • нет виртуальных функций-членов
  • нет инициализаторов данных-членов по умолчанию
(с C++11)
(до C++14)


Элементы агрегата:

  • для массива — элементы массива в порядке возрастания индексов, или
  • для класса — нестатические данные-члены, которые не являются анонимными полями-битами, в порядке объявления.
(до C++17)
  • для класса — прямые базовые классы в порядке объявления, за которыми следуют прямые нестатические данные-члены, которые не являются анонимными полями-битами и не являются членами анонимного объединения, в порядке объявления.
(с C++17)
  • для массива — элементы массива в порядке возрастания индексов,

Процесс

Результаты инициализации агрегата:

1) Отклоняются следующие некорректные случаи:
  • количество инициализаторов в списке инициализаторов превышает количество элементов агрегата, или
  • инициализация массива неизвестной размерности пустым списком инициализаторов ({}).
char cv[4] = {'a', 's', 'd', 'f', 0}; // error
int x[] = {}                          // error
2) Определяются явно инициализированные элементы агрегата следующим образом:
  • Если список инициализаторов является списком назначенных инициализаторов (агрегат может быть только типа класса), идентификатор в каждом назначенном инициализаторе должен называть прямой нестатический член данных класса, и явно инициализированные элементы агрегата — это элементы, которые содержат эти члены.
(с 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
3) Инициализируются каждый элемент агрегата в порядке элементов. То есть, все вычисления и побочные эффекты, связанные с данным элементом, выполняются до тех, которые связаны с последующими элементами в порядке.(с C++11).

Инициализация элементов

Для каждого явно инициализированного элемента:

  • Если элемент является анонимным членом объединения, и список инициализаторов является списком назначенных инициализаторов, элемент инициализируется списком назначенных инициализаторов {D}, где D — назначенный инициализатор, который называет член анонимного члена объединения. Должен быть только один такой назначенный инициализатор.
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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API