C++ 靜態初始化順序

最後更新: 2023-12-26 14:30:46 +0800 +0800
原文以英文撰寫。您正在閱讀AI翻譯版本。

C++ 會獨立於作用域指定儲存期。靜態儲存期與執行緒儲存期的初始化及銷毀規則,決定物件是在 main 之前可用、第一次使用時可用,或每個執行緒各自初始化一次。

儲存期與初始化階段 #

命名空間作用域物件、靜態資料成員,以及區塊作用域的 static 物件具有靜態儲存期。thread_local 物件具有執行緒儲存期:每個執行緒都有獨立的實例,並依據宣告與實作規則,在該執行緒啟動時或第一次使用時初始化。

當靜態儲存期或執行緒儲存期變數符合條件時,會進行常數初始化;否則,該變數會進行零初始化。常數初始化與零初始化合稱靜態初始化,並在動態初始化之前完成。因此,constinit 變數必須進行常數初始化,而 constexpr 物件是明確表達初始化要求的有效方式。

cpp
constinit int process_limit = 256;
constexpr int cache_line_bytes = 64;

constinit 不會使物件變成不可變;它只會拒絕需要動態初始化的初始化式。

順序邊界 #

同一翻譯單元中,一般的非內聯命名空間作用域變數會依定義順序進行動態初始化。跨翻譯單元的動態初始化沒有可靠的原始碼順序:它可能以不確定順序排序,或受到延遲初始化規則的影響。因此,讀取另一個非區域物件的非區域初始化式,可能只會看到零初始化狀態,而不是該物件預期的動態狀態。

函式區域靜態物件會將建構延遲到控制流程第一次經過其宣告時:

cpp
Logger& logger() {
    static Logger instance;
    return instance;
}

從 C++11 開始,對區塊作用域靜態物件的並行初始化嘗試只會成功初始化一次;其他執行緒會等待初始化成功。如果初始化式擲出例外,則會在後續進入時重試初始化。第一次使用時建構可移除 instance 的跨翻譯單元建構相依性,但呼叫端仍必須建立更廣泛的關閉策略。

銷毀是獨立的設計問題 #

靜態物件與執行緒物件分別在程式終止或執行緒結束時銷毀。不能將跨翻譯單元的非區域物件銷毀順序作為安全的相依機制,因為解構函式可能存取已經銷毀的物件。函式區域靜態物件會依建構完成順序的反向順序銷毀;當其解構函式呼叫其他存取器時,也可能產生類似的風險。

對於在結束處理期間互相作用的資源,應優先採用明確的所有權與關閉順序。對於存續於整個程序生命週期的服務,如果選擇由作業系統回收資源的契約,可以刻意不銷毀這些服務;應記錄此選擇,而不要依賴偶然的銷毀順序。

評論

在 GitHub 上閱讀或評論(發表評論需要登入)