4

我最近變得痛苦地意識到Static Initialization Order Fiasco。我想知道,如果「初始化順序在翻譯單元中未定義」的規則仍然適用於子類中的靜態成員所需的父類中的靜態成員。靜態類初始化階段

例如,假設我們(不包括,爲簡便起見,所有的#警衛和包括)

// a.h 
class A { 
    static int count; 
    static int register_subclass(); 
}; 

// a.cpp 
int A::count = 0; 
int A::register_subclass() { 
    return count ++; 
} 

然後的A一個子類,

// b.h 
class B : public A { 
    static int id; 
}; 

// b.cpp 
int B::id = A::register_subclass(); 

有兩種翻譯單元在這裏與一個靜態對象取決於另一個初始化中的靜態對象...它似乎可能是一個靜態初始化順序失敗的實例。

我的問題是:它實際上是安全

也就是說,我保證B::id不會包含從A::count複製的垃圾,在垃圾箱被初始化之前?從我自己的測試中,A似乎總是首先被初始化,但我不確定如何在初始化順序中引入噪聲,以在行爲未定義時增加失敗的可能性。

+3

否。將a.cpp和b.cpp鏈接到可執行文件的順序*不*保證。 *表示「初始化順序失敗」。 (您的鏈接器可能使用字母順序,或者它可能不會)。 –

+0

@BoPersson我害怕這一點。謝謝。讓這個答案(也許提供一個參考?),我會將它標記爲接受 – stett

+0

@BoPersson:答案去那裏好友↓↓↓↓↓↓↓↓↓↓ –

回答

3

一般來說,它是不能完全依靠一個基類和派生類的靜態初始化順序。不能保證A的靜態初始化將在B之前發生。這是static initialization order fiasco的定義。根據然而,他說,波格丹在評論中指出

您可以使用結構在第一次使用成語:

// a.h 
class A { 
private: 
    static int& count(); 
protected: 
    static int register_subclass(); 
}; 

// a.cpp 
int& A::count() { 
    static int count = 0; 
    return count; 
} 

int A::register_subclass() { 
    return count()++; 
} 

// b.h 
class B : public A { 
public: 
    static int id; 
}; 

// b.cpp 
int B::id = A::register_subclass(); 

Live demo.

更新標準中的[3.6.2]中,本具體示例中的初始化順序是有保證的。它與繼承無關,但與A::count的初始化是不斷初始化,這是保證在動態初始化之前完成的事實,這是B::id使用什麼。

但是,除非你有這樣intracaccies的完整把握,我建議你使用結構在第一次使用成語。

而正是在這種情況下確定,但在多線程方面要小心像A::register_subclass功能。如果多個線程同時調用它,則會發生任何事情

+0

謝謝 - 這實際上是我最終使用的解決方案。這太糟糕了,有必要在我可憐的簡單的小變量周圍添加那些令人討厭的靜態函數。 – stett

+0

實際上,根據標準中的[3.6.2],OP的具體示例中的初始化順序是有保證的。它與繼承沒有任何關係,但事實上'A :: count'的初始化是*常量初始化*,這是保證在動態初始化之前完成的,這是'B :: id'使用的。然而,'count ++'可能會在線程中出現問題 - 'B :: id'和其他一些'C :: id'的初始化可能會同時運行。 cc @stett – bogdan

+0

使用本地'靜態'的建議通常很合理,但爲了準確性,我認爲可能需要在答案中澄清上面的細節。 – bogdan

0

我在想,如果「初始化順序在轉換單元中未定義」的規則仍然適用於子類中的靜態成員所需的父類中的靜態成員。

是的,它的作用。

靜態數據成員與繼承層次結構(或者真的,它們的封裝類完全相關)的唯一方法是完全限定名稱;他們的定義和初始化完全沒有意識到/不理解這一點。