命名空间
变体
操作

初始化

来自 cppreference.cn
< cpp‎ | 语言
 
 
C++ 语言
 
 

变量的初始化在构造时提供其初始值。

初始值可以在声明符new 表达式的初始化器部分提供。它也发生在函数调用期间:函数参数和函数返回值也会被初始化。

目录

[编辑] 初始化器

对于每个声明符,初始化器(如果存在)可以是以下之一

= 表达式 (1)
= {}
= { 初始化器-列表 }
= { 指定初始化器-列表 }
(2)

(C++20 起)
( 表达式-列表 )
( 初始化器-列表 )
(3) (C++11 前)
(C++11 起)
{}
{ 初始化器-列表 }
{ 指定初始化器-列表 }
(4) (C++11 起)
(C++11 起)
(C++20 起)
1) 复制初始化语法。
2) 聚合初始化语法。(C++11 前)列表初始化语法。(C++11 起)
3) 直接初始化语法。
4) 列表初始化语法。
表达式 - 任何表达式(除了未加括号的逗号表达式
表达式-列表 - 逗号分隔的表达式列表(除了不带括号的逗号表达式)
初始化器-列表 - 逗号分隔的初始化子句列表(见下文)
指定初始化器-列表 - 逗号分隔的指定初始化子句列表


一个初始化子句可以是以下之一

表达式 (1)
{} (2)
{ 初始化器-列表 } (3)
{ 指定初始化器-列表 } (4) (C++20 起)

语法 (2-4) 统称为花括号括起来的初始化器列表

[编辑] 初始化器语义

如果未为对象指定初始化器,则对象是默认初始化的。如果未为引用指定初始化器,则程序格式错误。

如果为对象指定的初始化器是 ()(由于语法限制不能出现在声明符中),则对象是值初始化的。如果为引用指定的初始化器是 (),则程序格式错误。

初始化器的语义如下

  • 如果被初始化实体是一个引用,请参见引用初始化
  • 否则,被初始化实体是一个对象。给定对象的类型为T
  • 如果初始化器是语法 (2)
(C++11 前)
  • 如果初始化器是语法 (2)(4),则对象是列表初始化的。
(C++11 起)
#include <string>
 
std::string s1;           // default-initialization
std::string s2();         // NOT an initialization!
                          // actually declares a function “s2”
                          // with no parameter and returns std::string
std::string s3 = "hello"; // copy-initialization
std::string s4("hello");  // direct-initialization
std::string s5{'a'};      // list-initialization (since C++11)
 
char a[3] = {'a', 'b'}; // aggregate initialization
                        // (part of list initialization since C++11)
char& c = a[0];         // reference initialization

[编辑] 非局部变量

所有具有静态存储期的非局部变量都在程序启动时初始化,早于main 函数的执行开始(除非延迟,见下文)。所有具有线程局部存储期的非局部变量都在线程启动时初始化,早于线程函数的执行开始。对于这两类变量,初始化发生在两个不同的阶段

[编辑] 静态初始化

静态初始化有两种形式

1) 如果可能,应用常量初始化
2) 否则,非局部静态和线程局部变量会零初始化

实践中

  • 常量初始化通常在编译时应用。预先计算的对象表示作为程序映像的一部分存储。如果编译器不这样做,它仍然必须保证初始化发生在任何动态初始化之前。
  • 要零初始化的变量被放置在程序映像的 .bss 段中,该段不占用磁盘空间,并在加载程序时由操作系统清零。

[编辑] 动态初始化

所有静态初始化完成后,非局部变量的动态初始化在以下情况下发生

1) 无序动态初始化,仅适用于(静态/线程局部)类模板的静态数据成员变量模板(C++14 起),它们未被显式特化。此类静态变量的初始化与所有其他动态初始化之间是无序的,除非程序在变量初始化前启动了一个线程,在这种情况下其初始化是无序的(C++17 起)。此类线程局部变量的初始化与所有其他动态初始化之间是无序的。
2) 部分有序动态初始化,适用于所有不是隐式或显式实例化特化的内联变量。如果部分有序的 V 在每个翻译单元中都在有序或部分有序的 W 之前定义,则 V 的初始化在 W 的初始化之前排序(如果程序启动一个线程,则为 happens-before)。
(C++17 起)
3) 有序动态初始化,适用于所有其他非局部变量:在单个翻译单元内,这些变量的初始化总是按照它们在源代码中定义的精确顺序排序。不同翻译单元中静态变量的初始化是无序的。不同翻译单元中线程局部变量的初始化是无序的。

如果具有静态或线程存储期的非局部变量的初始化通过异常退出,则调用 std::terminate

[编辑] 早期动态初始化

如果以下条件都为真,编译器允许将动态初始化的变量作为静态初始化的一部分进行初始化(实质上是在编译时)

1) 初始化的动态版本在其初始化之前不会改变任何其他命名空间范围对象的值
2) 初始化的静态版本在初始化变量中产生的值与所有不需要静态初始化的变量都动态初始化时,动态初始化产生的值相同。

由于上述规则,如果某个对象o1的初始化引用了命名空间范围对象o2(可能需要动态初始化,但在同一翻译单元中稍后定义),则使用的o2的值是完全初始化的o2的值(因为编译器将o2的初始化提升到编译时),还是仅仅是零初始化的o2的值,这是未指定的。

inline double fd() { return 1.0; }
 
extern double d1;
 
double d2 = d1;   // unspecified:
                  // dynamically initialized to 0.0 if d1 is dynamically initialized, or
                  // dynamically initialized to 1.0 if d1 is statically initialized, or
                  // statically initialized to 0.0 (because that would be its value
                  // if both variables were dynamically initialized)
 
double d1 = fd(); // may be initialized statically or dynamically to 1.0

[编辑] 延迟动态初始化

动态初始化是在 main 函数(对于静态变量)或线程的初始函数(对于线程局部变量)的第一个语句之前发生,还是延迟到之后发生,这是由实现定义的。

如果非内联变量的(C++17 起)初始化被延迟到 main/线程函数的第一个语句之后发生,它将在与要初始化的变量在同一翻译单元中定义的任何具有静态/线程存储期的变量的第一次ODR-使用之前发生。如果从给定翻译单元没有 ODR-使用任何变量或函数,则在该翻译单元中定义的非局部变量可能永远不会被初始化(这模拟了按需动态库的行为)。然而,只要从翻译单元中 ODR-使用任何内容,所有其初始化或析构具有副作用的非局部变量都将被初始化,即使它们在程序中没有被使用。

如果内联变量的初始化被延迟,它将在该特定变量的第一次ODR-使用之前发生。

(C++17 起)
// ============
// == File 1 ==
 
#include "a.h"
#include "b.h"
 
B b;
A::A() { b.Use(); }
 
// ============
// == File 2 ==
 
#include "a.h"
 
A a;
 
// ============
// == File 3 ==
 
#include "a.h"
#include "b.h"
 
extern A a;
extern B b;
 
int main()
{
    a.Use();
    b.Use();
}
 
// If a is initialized before main is entered, b may still be uninitialized
// at the point where A::A() uses it (because dynamic initialization is
// indeterminately sequenced across translation units)
 
// If a is initialized at some point after the first statement of main (which odr-uses
// a function defined in File 1, forcing its dynamic initialization to run),
// then b will be initialized prior to its use in A::A

[编辑] 静态局部变量

对于局部(即块作用域)静态和线程局部变量的初始化,请参阅静态块变量

在具有外部或内部链接的变量的块作用域声明中不允许使用初始化器。这样的声明必须带有 extern,并且不能是定义。

[编辑] 类成员

非静态数据成员可以用成员初始化器列表默认成员初始化器进行初始化。

[编辑] 注意

非局部变量的析构顺序在 std::exit 中描述。

[编辑] 缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 发布时的行为 正确的行为
CWG 270 C++98 类模板的静态数据成员的初始化顺序
未指定
除了显式特化和定义外,指定为无序
指定为无序,除了
CWG 441 C++98 具有静态存储期的非局部引用
并非总是在动态初始化之前初始化
被视为静态初始化,总是在
动态初始化之前初始化
CWG 1415 C++98 块作用域的 extern 变量
声明可能是一个定义
禁止(此类声明中不允许使用初始化器)
禁止(此类声明中不允许
CWG 2599 C++98 不清楚在初始化器中评估函数
参数是否是初始化的一部分
它是初始化的一部分

[编辑] 另请参阅

C 文档 关于 初始化