Namespaces
Variants

Aggregate initialization

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

从初始化列表初始化聚合体 。这是 列表初始化 的一种形式 (since 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 起)
1,2) 使用普通初始化列表对聚合体进行初始化。
3,4) 使用 指定初始化器 初始化聚合体(仅限聚合类)。

定义

聚合体

一个 聚合体 是以下类型之一:

  • 数组类型
  • 具有以下特征的类类型
  • 无用户声明的构造函数
(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 { 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}; // 错误:初始化子句过多

初始化过程

确定元素类型

聚合初始化的效果是:

1) 按如下方式确定聚合体的 显式初始化元素
  • 如果初始化列表是 指派初始化器列表 (该聚合体只能是类类型),则每个指示符中的标识符必须命名类的直接非静态数据成员,且被显式初始化的聚合体元素是那些成员本身或包含这些成员的元素。
(since C++20)
  • 否则, (C++20 起) 若初始化列表非空,则聚合的显式初始化元素为:具有附属初始化子句的元素,以及具有带附属初始化子句的子聚合的元素。
  • 否则,初始化列表必须为空( { } ),且不存在显式初始化的元素。
如果聚合体是联合体且存在两个或更多显式初始化的元素,则程序非良构:
union u { int a; const char* b; };
u a = {1};                   // 正确:显式初始化成员 `a`
u b = {0, "asdf"};           // 错误:显式初始化两个成员
u c = {"asdf"};              // 错误:int 类型无法通过 "asdf" 初始化
// C++20 指定初始化器列表
u d = {.b = "asdf"};         // 正确:可显式初始化非首成员
u e = {.a = 1, .b = "asdf"}; // 错误:显式初始化两个成员
2) 按元素顺序初始化聚合体的每个元素 。也就是说,与给定元素相关联的所有值计算和副作用都 先序于 其后续元素的任何操作 (C++11 起)

显式初始化元素

对于每个显式初始化的元素:

  • 若该元素为匿名联合体成员且初始化器列表为 指派初始化器列表 ,则该元素由指派初始化器列表 { D } 初始化,其中 D 是指名该匿名联合体成员的指派初始化器子句。此类指派初始化器子句应当仅有一个。
struct C
{
    union
    {
        int a;
        const char* p;
    };
    int x;
} c = {.a = 1, .x = 3}; // 以 1 初始化 c.a,以 3 初始化 c.x
  • 否则,若初始化器列表为指派初始化器列表,则该元素以对应指派初始化器子句的初始化器进行初始化。
  • 若该初始化器采用 语法 (1) ,且需要进行窄化转换以转换该表达式,则程序非良构。
(C++20 起)


  • 初始化器列表是一个花括号包围的初始化器列表:
(until C++20)
  • 否则,初始化器列表是一个非指定符的花括号包围初始化器列表:
(since C++20)
  • 若初始化子句属于该聚合元素,则聚合元素从该初始化子句 进行拷贝初始化
  • 否则,该聚合元素从由所有属于其子对象的初始化子句(按出现顺序)组成的花括号初始化列表进行拷贝初始化。
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)
  • 否则,若该元素不是引用类型,则通过空初始化列表对该元素进行 拷贝初始化
  • 否则,程序非良构。
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) 被称为指定初始化器:每个 指示符 必须命名 T 的直接非静态数据成员,且表达式中使用的所有 指示符 必须按照 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

由指定初始化器命名的每个直接非静态数据成员,都会根据指示符后的对应花括号或等号初始化器进行初始化。禁止窄化转换。

指定初始化器可用于将 联合体 初始化为非首成员的状态。联合体只能提供一个初始化器。

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
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

若使用指定初始化器子句初始化的聚合类型包含匿名联合体成员,则对应的指定初始化器必须命名该匿名联合体的某个成员。

注意:乱序指定初始化、嵌套指定初始化、混合指定初始化器与常规初始化器、以及数组的指定初始化在 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)
(since 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";
// 等价于 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语言中,大小比字符串字面值长度小一的字符数组可以用字符串字面值初始化;这样得到的数组不是空字符结尾的。这在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 兼容性与可移植性修复( 允许从 UTF-8字符串字面量 初始化 unsigned char 数组 )
__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}}; // 完全使用大括号的二维数组:{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};         // 正确,联合的第一个成员
//  U u2 = {0, "asdf"}; // 错误:联合的初始化器过多
//  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++标准。

缺陷报告 适用版本 发布时行为 正确行为
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
unsigned char 数组,与C或C++17不兼容
此类初始化
有效

参见

C 文档 关于 结构体和联合体初始化