2013-12-15 132 views
10

gcc是否對靜態成員初始化時間有任何保證,特別是關於模板類?gcc中模板的非延遲靜態成員初始化?

我想知道我是否能夠很好地保證靜態成員(PWrap_T<T>::p_s)將在main()之前初始化,當時類在多個編譯單元中實例化。在main的開始處嘗試手動觸摸每個編譯單元中的符號是不現實的,但我不清楚其他任何方法都可行。

我已經喜歡在不同的單位bar()總是得到期望的結果的方法測試,但我需要知道當/如果曾經GCC將抽出地毯出來,無論是可以預防的。

此外,在庫加載完成之前,DSO中的所有靜態成員都會被初始化嗎?

#include <iostream> 
#include <deque> 

struct P; 
inline std::deque<P *> &ps() { static std::deque<P *> d; return d; } 
void dump(); 

struct P { 
    P(int id, char const *i) : id_(id), inf_(i) { ps().push_back(this); } 
    void doStuff() { std::cout << id_ << " (" << inf_ << ")" << std::endl; } 
    int const  id_; 
    char const *const inf_; 
}; 

template <class T> 
struct PWrap_T { static P p_s; }; 

// *** Can I guarantee this is done before main()? *** 
template <class T> 
P PWrap_T<T>::p_s(T::id(), T::desc()); 

#define PP(ID, DESC, NAME) /* semicolon must follow! */ \ 
struct ppdef_##NAME {         \ 
    constexpr static int   id() { return ID; }  \ 
    constexpr static char const *desc() { return DESC; } \ 
};              \ 
PWrap_T<ppdef_##NAME> const NAME 

// In a compilation unit apart from the template/macro header. 
void dump() { 
    std::cout << "["; 
    for (P *pp : ps()) { std::cout << " " << pp->id_ << ":" << pp->inf_; } 
    std::cout << " ]" << std::endl; 
} 

// In some compilation unit. 
void bar(int cnt) { 
    for (int i = 0; i < cnt; ++i) { 
    PP(2, "description", pp); 
    pp.p_s.doStuff(); 
    } 
} 

int main() { 
    dump(); 
    PP(3, "another", pp2); 
    bar(5); 
    pp2.p_s.doStuff(); 
} 

(C++ 11§3.6.2/ 4 - [basic.start.init] :)

它實現定義是否一個非本地的動態初始化具有靜態存儲持續時間的變量在main的第一條語句之前完成。 如果初始化被推遲到main的第一條語句之後的某個時間點,它應該在第一個odr-use(3.2)之前出現,在任何函數或變量中定義在同一個翻譯單元中作爲要初始化的變量。

...具有初始化帶副作用的靜態存儲持續時間的非局部變量必須初始化,即使它沒有使用odr(3.2,3.7.1)。

此外,試圖__attribute__ ((init_priority(int)))__attribute__ ((constructor))爲模板成員的初始化產生warning: attributes after parenthesized initializer ignored,我知道關於靜態初始化沒有其他招數。

在此先感謝任何能夠給我答案的人!

+0

我想象中的'ODR-use'規則意在包括可能有文件範圍的對象的動態共享對象(的DSO)。如果DSO在主啓動後被帶入'dlopen()',那麼顯然不能初始化DSO中的所有內容,但理論上'dlopen()'可以在調用DSO中的其他任何內容之前確保DSO中的所有內容都已初始化。我想最終的答案是由ABI爲您編譯的任何操作系統/體系結構定義的。 –

+0

單身模式解決了這個問題,不是嗎? – lkanab

回答

2

該標準保證靜態存儲持續時間對象在與外部源一起使用與您的對象相同的翻譯單元中的任何功能/變量之前被初始化。

這裏的措辭旨在與共享庫協同工作。由於共享庫可以在main()啓動後動態加載,因此語言規範必須足夠靈活以應對它。但只要你從翻譯單位之外訪問你的對象,那麼你就可以保證它在你被授予訪問權限之前就已經被構建了(除非你正在做一些病態的事情)。

但是如果它在同一編譯單元中的另一個靜態存儲持續時間對象的構造函數中使用,則不會停止在初始化之前使用它。

但是,您可以通過使用下面的技術輕鬆手動提供保證,以便在使用之前正確初始化靜態對象。

只需將靜態變量更改爲靜態函數即可。然後在函數內部聲明一個返回的靜態成員。所以你可以像以前一樣使用它(只需添加())。

template <class T> 
struct PWrap_T 
{ 
    static P& p_s(); // change static variable to static member function. 
         // Rather than the type being P make it a reference to P 
         // because the object will be held internally to the function 
}; 

template <class T> 
P& PWrap_T<T>::p_s() 
{ 
    // Notice the member is static. 
    // This means it will live longer than the function. 
    // Also it will be initialized on first use. 
    // and destructed when other static storage duration objects are destroyed. 
    static P p_s_item(T::id(), T::desc()); 

    return p_s_item; 

    // Note its not guaranteed to be created before main(). 
    // But it is guaranteed to be created before first use. 
} 

所以在這裏你可以得到一個全球性的好處(無論它們是什麼)。你可以得到有保證的施工/銷燬,並且你知道物體在被使用之前會被正確地構造。

你需要做的唯一的變化是:

void bar(int cnt) { 
    for (int i = 0; i < cnt; ++i) { 
    PP(2, "description", pp); 
    pp.p_s().doStuff(); 
    // ^^ Add the braces here. 
    } 
} 
+0

唯一的問題是,在pre-C++ 11 iirc中,函數靜態不是線程安全的(至少是它們的初始化)。 – Bwmat

+0

@Bwmat:真的,語言並不能保證它在C++ 11之前。但是gcc確實(甚至在C++ 11之前)。 –

+1

@Bwmat:http://gcc.gnu.org/ml/gcc-patches/2004-09/msg00265.html –

1

正如你已經發現C++標準並不保證「靜態存儲持續時間的非局部變量的動態初始化是在main的第一個語句之前完成的」。但是,GCC確實在執行main之前進行初始化,如How Initialization Functions Are Handled中所述。

唯一的問題是從加載了dlopen的共享庫中初始化靜態對象。這些只會在圖書館被加載時初始化,但你無能爲力。