Initialization
初始化 是指变量在构造时提供其初始值的过程。
初始值可以在 声明符 的初始化器部分或 new表达式 中提供。函数调用期间也会进行初始化:函数形参和函数返回值同样会被初始化。
目录 |
初始化器
对于每个声明符, 初始化器 (若存在)可为下列形式之一:
=
表达式
|
(1) | ||||||||
= {}
= {
初始化列表
}
= {
指定初始化列表
}
|
(2) |
(自 C++20 起) |
|||||||
(
表达式列表
)
(
初始化列表
)
|
(3) |
(C++11 前)
(自 C++11 起) |
|||||||
{}
{
初始化列表
}
{
指定初始化列表
}
|
(4) |
(自 C++11 起)
(自 C++11 起) (自 C++20 起) |
|||||||
| expression | - | 任意表达式(除未加括号的 逗号表达式 外) |
| expression-list | - | 以逗号分隔的表达式列表(除未加括号的逗号表达式外) |
| initializer-list | - | 以逗号分隔的初始化子句列表(详见下文) |
| designated-initializer-list | - | 以逗号分隔的 指派初始化子句 列表 |
一个
初始化子句
可以是以下之一:
| 表达式 | (1) | ||||||||
{}
|
(2) | ||||||||
{
初始化列表
}
|
(3) | ||||||||
{
指派初始化列表
}
|
(4) | (C++20 起) | |||||||
语法 (2-4) 统称为 花括号初始化列表 。
初始化器语义
如果未为对象指定初始化器,则该对象将进行 默认初始化 。如果未为 引用 指定初始化器,则程序属于错误形式。
如果为对象指定的初始化器是 ( ) (由于语法限制无法出现在声明符中),则该对象进行 值初始化 。如果为引用指定的初始化器是 ( ) ,则程序非良构。
初始化器的语义如下:
- 如果被初始化的实体是引用,请参阅 引用初始化 。
-
否则,被初始化的实体是一个对象。假设该对象的类型为
T:
-
- 如果初始化器采用语法 (1) ,则该对象进行 复制初始化 。
| (C++11 前) | |
|
(C++11 起) |
-
- 若初始化器采用 (3) 语法形式,则对象将执行 直接初始化 。
#include <string> std::string s1; // 默认初始化 std::string s2(); // 并非初始化! // 实际声明了一个无参数且返回 std::string 的函数 "s2" std::string s3 = "hello"; // 拷贝初始化 std::string s4("hello"); // 直接初始化 std::string s5{'a'}; // 列表初始化(自 C++11 起) char a[3] = {'a', 'b'}; // 聚合初始化 //(自 C++11 起成为列表初始化的一部分) char& c = a[0]; // 引用初始化
非局部变量
所有具有静态 存储期 的非局部变量会在程序启动时、 main 函数 开始执行之前进行初始化(除非被延迟,见下文)。所有具有线程局部存储期的非局部变量会在线程启动时、线程函数开始执行之前进行初始化。对于这两类变量,初始化会发生在两个不同的阶段:
静态初始化
静态初始化有两种形式:
在实践中:
- 常量初始化通常在编译时执行。预计算的对象表示会作为程序映像的一部分存储。如果编译器未执行此操作,它仍必须保证该初始化在任何动态初始化之前发生。
-
需要零初始化的变量会被放置在程序映像的
.bss段中,该段在磁盘上不占空间,并在程序加载时由操作系统清零。
动态初始化
在所有静态初始化完成后,非局部变量在以下情况下进行动态初始化:
|
2)
部分有序动态初始化
,适用于所有非隐式或显式实例化特化的内联变量。若在每个翻译单元中部分有序的 V 在有序或部分有序的 W 之前定义,则 V 的初始化在 W 的初始化之前排序(若程序启动线程,则为先发生于)。
|
(since C++17) |
如果具有静态或线程存储期的非局部变量在初始化过程中因异常退出,将调用 std::terminate 。
早期动态初始化
编译器被允许在以下两个条件同时满足时,将动态初始化的变量作为静态初始化(本质上是在编译时)的一部分进行初始化:
由于上述规则,如果某个对象
o1
的初始化引用了命名空间作用域对象
o2
(该对象可能需要进行动态初始化),但
o2
定义在同一翻译单元的后续位置,则使用的
o2
值是否是完全初始化后的
o2
值(因为编译器将
o2
的初始化提升至编译期),还是仅经过零初始化的
o2
值,这是未指定的。
inline double fd() { return 1.0; } extern double d1; double d2 = d1; // 未指定: // 若d1为动态初始化,则动态初始化为0.0;或 // 若d1为静态初始化,则动态初始化为1.0;或 // 静态初始化为0.0(因为若两个变量均为动态初始化,此值将为其初始值) double d1 = fd(); // 可能静态或动态初始化为1.0
延迟动态初始化
动态初始化是否发生于 main 函数首条语句之前(对于静态变量)或线程初始函数之前(对于线程局部变量),或是延迟到之后发生,这是由实现定义的。
如果非内联变量 的初始化 (C++17 起) 被推迟到 main/线程函数的第一条语句之后发生,那么它会在同一翻译单元中定义的、具有静态/线程存储期的任何变量的首次 ODR-use 之前发生。如果给定翻译单元中没有变量或函数被 ODR-use,那么定义在该翻译单元中的非局部变量可能永远不会被初始化(这模拟了按需动态库的行为)。然而,只要翻译单元中的任何实体被 ODR-use,所有具有副作用的初始化或析构的非局部变量都会被初始化,即使它们在程序中没有被使用。
|
如果内联变量的初始化被延迟,它会在该特定变量的首次 ODR-use 之前发生。 |
(since C++17) |
// ============ // == 文件 1 == #include "a.h" #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() { a.Use(); b.Use(); } // 如果 a 在进入 main 函数前初始化,在 A::A() 使用 b 时 b 可能仍未初始化 // (因为动态初始化在翻译单元间的顺序是不确定的) // 如果 a 在 main 函数第一条语句后的某个时刻初始化(此时会 odr-use 文件 1 中定义的函数, // 强制其动态初始化执行),那么 b 将在 A::A 中使用它之前完成初始化
静态局部变量
对于局部(即块作用域)静态变量和线程局部变量的初始化,请参阅 静态块变量 。
在具有 外部或内部链接 的块作用域变量声明中不允许使用初始化器。此类声明必须与 extern 同时出现,且不能是定义。
类成员
非静态数据成员可以通过 成员初始化列表 或使用 默认成员初始化器 进行初始化。
注释
非局部变量的析构顺序在 std::exit 中说明。
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 适用标准 | 发布时行为 | 正确行为 |
|---|---|---|---|
| CWG 270 | C++98 | 类模板静态数据成员的初始化顺序未作规定 | 规定为无序初始化(显式特化和定义除外) |
| CWG 441 | C++98 | 具有静态存储期的非局部引用并不总是在动态初始化前完成初始化 | 视为静态初始化,始终在动态初始化前完成初始化 |
| CWG 1415 | C++98 | 块作用域的 extern 变量声明可能构成定义 | 禁止此类声明包含初始化器 |
| CWG 2599 | C++98 | 在初始化器中计算函数参数是否属于初始化过程未明确说明 | 明确属于初始化过程 |
参见
|
C 文档
关于
初始化
|