命名空间
变体
操作

聚合初始化

来自 cppreference.cn
< cpp‎ | language
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句(循环)
for
range-for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (在 C++11 中弃用*)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储期说明符
初始化
聚合初始化
列表初始化 (C++11)  
常量初始化
引用初始化

 
 

从初始化器列表初始化聚合。它是列表初始化的一种形式(自 C++11 起)

目录

[编辑] 语法

T 对象 = { arg1, arg2, ... }; (1)
T 对象 { arg1, arg2, ... }; (2) (自 C++11 起)
T 对象 = { .des1 = arg1 , .des2 { arg2 } ... }; (3) (自 C++20 起)
T 对象 { .des1 = arg1 , .des2 { arg2 } ... }; (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 { 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

[编辑] 初始化过程

[编辑] 确定元素种类

聚合初始化的效果是

1) 如下确定聚合的显式初始化的元素
  • 如果初始化器列表是指定初始化器列表(聚合只能是类类型),则每个指定符中的标识符应命名类的直接非静态数据成员,并且聚合的显式初始化的元素是那些成员或包含这些成员的元素。
(自 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
2) 按元素顺序初始化聚合的每个元素。也就是说,与给定元素关联的所有值计算和副作用都在先于顺序中跟随它的任何元素的计算和副作用(自 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
  • 否则,如果初始化器列表是指定初始化器列表,则该元素使用相应指定初始化器子句的初始化器进行初始化。
  • 如果该初始化器是语法 (1),并且需要进行窄化转换才能转换表达式,则程序是非良构的。
(自 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) 被称为指定初始化器:每个 designator 必须命名 T 的直接非静态数据成员,并且表达式中使用的所有 designator  的出现顺序必须与 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 起)

[编辑] 字符数组

普通字符类型(charsigned charunsigned charchar8_t(自 C++20 起)char16_tchar32_t(自 C++11 起)wchar_t 的数组可以分别从普通字符串字面量、UTF-8 字符串字面量(自 C++20 起)、UTF-16 字符串字面量、UTF-32 字符串字面量(自 C++11 起)或宽字符串字面量初始化,可以选择用花括号括起来。此外,charunsigned 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 中,大小比字符串字面量大小小 1 的字符数组可以用字符串字面量初始化;结果数组不是空终止的。C++ 中不允许这样做。

特性测试宏 Std 特性
__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}; // 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 数组
unsigned char,这与 C 或 C++17 不兼容
这种初始化
是有效的

[编辑] 参见

C 文档 关于 Struct 和 union 初始化