初始化

出自cppreference.com


 
 
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
  • 如果使用的是語法 (1) 的初始化器,那麼對象會複製初始化
  • 如果使用的是語法 (2) 的初始化器:
(C++11 前)
  • 如果使用的是語法 (2)(4) 的初始化器,那麼對象會列表初始化
(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 函數的執行之前進行(除非被延遲,見下文)。所有具有線程局部存儲期的非局部變量的初始化,會作為線程啟動的一部分進行,並按順序早於線程函數的執行開始。對於這兩種變量,初始化發生於兩個截然不同的階段:

靜態初始化

靜態初始化有兩種形式:

1) 如果可能,就應用常量初始化
2) 否則,非局部的靜態及線程局域變量會被零初始化

實踐中:

  • 常量初始化通常在編譯期進行。預先被計算的對象表示會作為程序映像的一部分存儲下來。如果編譯器沒有這樣做,那麼它仍然必須保證該初始化發生早於任何動態初始化。
  • 零初始化的變量將被置於程序映像的 .bss 段,它不佔據磁盤空間,並在加載程序時由作業系統以零填充。

動態初始化

在所有靜態初始化完成後,在下列情形中進行非局部變量的動態初始化:

1) 無序的動態初始化,僅適用於未被顯式特化的(靜態/線程局域)類模板的靜態數據成員變量模板(C++14 起)。這些靜態變量的初始化相對於所有其他動態初始化之間是順序不確定的,除非程序在初始化某個變量之前開始了一個線程,此時初始化則是無順序的(C++17 起)。這些線程局域變量的初始化相對於所有其他動態初始化之間是無順序的。
2) 部分有序的動態初始化,適用於並未被隱式或顯式實例化的特化的所有內聯變量。如果一個部分有序的 V 在每個翻譯單元中比有序或部分有序的 W 更早定義,那麼 V 的初始化按順序早於(或若程序啟動了線程,則為先發生於)W 的初始化。
(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;   // 未指明:
                  // 如果 d1 被动态初始化则动态初始化为 0.0,或
                  // 如果 d1 被静态初始化则动态初始化为 1.0,或
                  // 静态初始化为 0.0(因为当两个变量都被动态初始化时将为这个值)

double d1 = fd(); // 可能静态或动态初始化为 1.0

延遲動態初始化

動態初始化是���生早於(對於靜態變量)主函數或(對於線程局域變量)其線程的啟動函數的首條語句,還是延遲到發生晚於它們,是由實現定義的。

如果非內聯變量的(C++17 起)初始化延遲到發生晚於主/線程函數的首條語句,那麼它發生早於與所初始化的變量定義於同一翻譯單元中的任何擁有靜態/線程存儲期的變量的首次 ODR 使用。如果給定翻譯單元中沒有 ODR 使用變量或函數,那麼在該翻譯單元定義的非局部變量可能始終不被初始化(這模仿按需的動態庫的行為)。然而,只要翻譯單元中 ODR 使用了任何事物,就會初始化所有在初始化或銷毀中擁有副作用的非局部變量,即使程序中沒有用到它們。

如果內聯變量的初始化被延遲,那麼它發生早於這個特定變量的首次 ODR 使用

(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 之前被初始化,那么 b 可能在 A::A() 使用它的时间点仍未被初始化
// (因为动态初始化在翻译单元间是顺序不确定的)

// 如果 a 在某个 main 的首条语句之后的时间点初始化
// (它 ODR 使用了定义于文件 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 文檔