初值可以由或 new 表达式的初始化器部分提供。在函数调用时也会发生:函数形参及函数返回值亦会被初始化。
对于每个声明符,初始化器必须是下列之一:
1) 括号中的任意表达式和花括号初始化器列表的逗号分隔列表
2) 等号后面跟着一个表达式
3) 花括号初始化器列表:表达式和其他花括号初始化器列表的可以为空的逗号分隔列表
根据其上下文,初始化器可以调用:
- ,例如 std::string s{};
- ,例如 std::string s("hello");
- ,例如 std::string s = "hello";
- ,例如 std::string s{'a', 'b', 'c'};
- ,例如 char a[3] = {'a', 'b'};
- 引用初始化,例如 char& c = a[0];
若不提供初始化器,则应用的规则。
静态初始化
1) 若受允许,则首先进行常量初始化(参见中符合情况的列表)。实践中,常量初始化通常在编译期进行,并将预先计算的对象表示作为程序映像的一部分存储下来。若编译器没有这么做,则亦保证此初始化发生早于任何动态初始化。
2) 对于所有其他非局部静态及线程局域变量,均进行零初始化。实践中,将被零初始化的变量置于程序映像的 .bss
段,它不占据磁盘空间,并在加载程序时为操作系统以零填充。
动态初始化
在所有静态初始化完成后,在下列情形中进行非局部变量的动态初始化:
1) 无序的动态初始化,仅适用于未被的(静态/线程局域)类模板静态数据成员及 (C++14 起)。这些静态变量的初始化相对于所有其他动态初始化之间是顺序不确定的,但若程序在初始化某个变量之前开始了一个线程,该情况下初始化则是无顺序的 (C++17 起)。这些线程局域变量的初始化相对于所有其他动态初始化之间是无顺序的。
3) 有序的动态初始化,适用于所有其他非局部变量:在单个翻译单元中,这些变量的初始化始终严格以其定义出现于源代码中的顺序定序。不同翻译单元中的静态变量的初始化之间是顺序不确定的。不同翻译单元中的线程局域变量的初始化之间是无顺序的。
当拥有静态或线程存储期的非局部变量的初始化通过异常退出时,调用 。
早期动态初始化
若下列条件皆为真,则允许编译器作为静态初始化(实为编译期)的一部分初始化动态初始化的变量:
2) 初始化的静态版本在被初始化变量中产生的值,与当所有不要求静态初始化的变量都被动态初始化时,由动态初始化所生成的值相同。
因为上述规则,若某对象 o1
的初始化涉及到命名空间作用域对象 o2
,而它潜在地要求动态初始化,但在同一翻译单元中在其之后定义,则所用的 是完全初始化的 o2
的值(因为编译器把 o2
的初始化提升到编译时)还是 o2
仅被零初始化的值,是未指明的。
延迟动态初始化
动态初始化是发生早于(对于静态变量)主函数或(对于线程局域变量)其线程的首个函数的首条语句,还是延迟到发生晚于它们,是由实现定义的。
如果非内联变量 (C++17 起)的初始化延迟到发生晚于主/线程函数的首条语句,则它发生早于与所初始化的变量定义于同一翻译单元中的任何拥有静态/线程存储期的变量的首次 ODR 式使用。若给定翻译单元中没有 ODR 式使用变量或函数,则定义于该翻译单元的非局部变量可能始终不被初始化(这模仿按需的动态库的行为)。然而,只要翻译单元中 ODR 式使用了任何事物,就会初始化所有在初始化或销毁中拥有副作用的非局部变量,即使程序中并不使用它们也是如此。
- // - 文件 1 -
- #include "b.h"
- B b;
- A::A(){ b.Use(); }
- // - 文件 2 -
- #include "a.h"
- A a;
- // - 文件 3 -
- #include "a.h"
- #include "b.h"
- extern A a;
- extern B b;
- int main() {
- b.Use();
- }
- // 若 a 在进入 main 之前被初始化,则 b 可能在 A::A() 使用它的时间点仍未被初始化
- // (因为动态初始化在翻译单元间是顺序不确定的)
- // 若 a 在某个 main 的首条语句之后的时间点初始化(它 ODR 式使用了定义于文件 1 的函数,强制其初始化得以运行),
- // 则 b 将在 A::A 使用它前初始化
有关局部(即块作用域)的静态和线程局部变量,见。
拥有外部或内部链接的变量的块作用域声明中不允许初始化器。这种声明必须带 extern 出现而且不能为定义。
非静态数据成员可以或以默认成员初始化器初始化。
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。