2009-11-30 120 views
37

對於靜態成員初始化,我使用嵌套幫助程序結構,對於非模板類可以正常工作。 但是,如果封閉類由模板參數化,則嵌套的初始化類未實例化,如果輔助對象未在主代碼中訪問。 爲了說明,一個簡化的例子(在我的情況下,我需要初始化一個向量)。C++靜態成員初始化(內部模板樂趣)

#include <string> 
#include <iostream> 

struct A 
{ 
    struct InitHelper 
    { 
     InitHelper() 
     { 
      A::mA = "Hello, I'm A."; 
     } 
    }; 
    static std::string mA; 
    static InitHelper mInit; 

    static const std::string& getA(){ return mA; } 
}; 
std::string A::mA; 
A::InitHelper A::mInit; 


template<class T> 
struct B 
{ 
    struct InitHelper 
    { 
     InitHelper() 
     { 
      B<T>::mB = "Hello, I'm B."; // [3] 
     } 
    }; 
    static std::string mB; 
    static InitHelper mInit; 

    static const std::string& getB() { return mB; } 
    static InitHelper& getHelper(){ return mInit; } 
}; 
template<class T> 
std::string B<T>::mB; //[4] 
template<class T> 
typename B<T>::InitHelper B<T>::mInit; 


int main(int argc, char* argv[]) 
{ 
    std::cout << "A = " << A::getA() << std::endl; 

// std::cout << "B = " << B<int>::getB() << std::endl; // [1] 
// B<int>::getHelper(); // [2] 
} 

隨着克++ 4.4.1:

  • [1]和[2]表示:

    A = Hello, I'm A.

    按預期工作

  • [1]未註釋:

    A = Hello, I'm A. 
    B =

    我所期望的,該InitHelper初始化的mB

  • [1]和[2]未註釋:
    A = Hello, I'm A. 
    B = Hello, I'm B.
    按預期工作
  • [1]評論,[2]未註釋:
    段錯誤在靜態初始化階段在[3]

因此我的問題:這是一個編譯器錯誤還是坐在監視器和椅子之間的錯誤? 如果後者是這種情況:是否有一個優雅的解決方案(即沒有顯式調用靜態初始化方法)?

更新I:
這似乎是一個期望的行爲(如在ISO/IEC C++ 2003標準,14.7.1定義):

除非類模板或一個成員成員模板已被顯式實例化或明確專用化,當需要成員定義存在的上下文中引用專用化時,成員的專業化被隱式實例化;特別是靜態數據成員的初始化(以及任何相關的副作用)不會發生,除非靜態數據成員本身以需要靜態數據成員定義存在的方式使用。

+0

Visual Studio 2008中具有相同的行爲(在靜態初始化-segfault) – 2009-11-30 11:08:27

+0

你爲什麼不只是寫的std ::串B :: MB = 「你好,我是B」? – 2009-11-30 11:11:27

+0

好吧,我看到在實際情況下,您需要無損傷媒介,抱歉。 – 2009-11-30 11:12:09

回答

36

這是前段時間在usenet上討論過的,而我試圖在stackoverflow上回答另一個問題:Point of Instantiation of Static Data Members。我認爲這是值得減少測試的情況下,並考慮隔離每個場景,所以讓我們來看看它更普遍的第一:


struct C { C(int n) { printf("%d\n", n); } }; 

template<int N> 
struct A { 
    static C c; 
}; 

template<int N> 
C A<N>::c(N); 

A<1> a; // implicit instantiation of A<1> and 2 
A<2> b; 

你有一個靜態數據成員模板的定義。這還沒有建立任何數據成員,因爲14.7.1

」 ......尤其是不發生靜態數據成員的初始化(和任何相關的副作用),除非靜態數據成員是本身以一種需要靜態數據成員定義存在的方式使用。「

東西(=實體)的定義是根據所述一個定義規則,其定義了字(在3.2/2)當該實體是「已使用」需要。特別是,如果所有引用都來自未經實例化的模板,則模板或表達式的成員或類似的事物不會「使用」實體(因爲它們要麼沒有進行潛在的評估,要麼只是不存在函數/成員函數本身使用),這樣一個靜態數據成員沒有實例化。

14.7.1/7的一個隱式實例化實例化了靜態數據成員的聲明 - 也就是說,它將實例化處理該聲明所需的任何模板。但是,它不會實例化定義 - 也就是說,初始化程序沒有實例化,並且該靜態數據成員類型的構造函數沒有隱式定義(標記爲「已使用」)。

這一切都意味着,上面的代碼將不會輸出任何東西。讓我們現在引起靜態數據成員的隱式實例化。

int main() { 
    A<1>::c; // reference them 
    A<2>::c; 
} 

這將導致兩個靜態數據成員存在,但問題是 - 如何初始化的順序?在一個簡單的讀,有人可能會認爲3.6.2/1適用,它說(由我強調):

「對象在同一翻譯單元在命名空間範圍內定義靜態存儲期限和動態初始化應初始化其定義出現在翻譯單元中的順序。「

現在在Usenet的帖子中說,並解釋in this defect report,這些靜態數據成員不會在翻譯單元中定義的,但它們被實例化在實例化單元,截至2.1/1解釋說:

檢查每個翻譯過的翻譯單元以產生一個需要的實例列表[注意:這可能包括已明確請求的實例(14.7.2)。]定義所需模板的定義。包含這些定義的翻譯單元的來源是r等於可用。 [注意:一個實現可以將足夠的信息編碼到翻譯的翻譯單元中,以確保這裏不需要源。 ]執行所有必需的實例以生成實例化單元。 [注意:這些翻譯單位與翻譯單位類似,但不包含對未經實例化的模板的引用,也不包含模板定義。 ]如果任何實例化失敗,該程序不合格。

實例化這樣的成員的點也並不重要,因爲實例化的這樣一個點是一個實例化和它的翻譯單元之間的上下文鏈接 - 它定義可見(截至14.6.4.1所規定的聲明,並且這些實例化點中的每一個必須給出具有相同含義的實例化,如在3.2/5的最後一個項目符號中的一個定義規則中所指定的)。如果我們想要有序的初始化,我們必須安排,所以我們不要混淆實例化,而是使用顯式聲明 - 這是顯式特化的區域,因爲它們與正常聲明並沒有真正的不同。事實上,C++ 0X改變了它的3.6.2措辭爲以下:

非本地對象具有靜態存儲持續時間的動態初始化或者有序的或無序的。 顯式專用類模板靜態數據成員的定義已經命令初始化。其他 類模板靜態數據成員(即,隱含或顯式實例化的特化)具有無序初始化。


這意味着你的代碼,即:

  • [1][2]評論:沒有參照靜態數據成員存在,所以它們的定義(也沒有它們的聲明,因爲不需要實例化B<int>)未被實例化。沒有副作用發生。
  • [1]未註釋:B<int>::getB()被使用,它本身使用B<int>::mB,它要求存在靜態成員。該字符串在main之前被初始化(任何情況下在該語句之前,作爲初始化非本地對象的一部分)。沒有使用B<int>::mInit,所以它沒有被實例化,所以沒有創建B<int>::InitHelper的對象,這使得它的構造函數不被使用,反過來也不會給B<int>::mB分配一些東西:您將只輸出一個空字符串。
  • [1][2]未註釋:本工作對你來說是運氣(或者相反:))。如上所述,不需要特定的初始化調用順序。它可能在VC++上工作,在GCC上失敗並在clang上工作。我們不知道。
  • [1]評論,[2]未註釋:同樣的問題 - 再次,靜態數據成員使用B<int>::mInitB<int>::getHelper使用,並且B<int>::mInit實例化將導致其構造函數中被實例化,這將使用B<int>::mB - 但是對於您的編譯器,在此特定運行中順序不同(未指定的行爲不需要在不同的運行中保持一致):首先初始化B<int>::mInit,它將在尚未構建的字符串對象上運行。
2
  • [1]未註釋的情況下: 這是確定。 static InitHelper B<int>::mInit不存在。如果模板類(struct)的成員未被使用,則不會被編譯。

  • [1]和[2]未註釋的案例: 沒關係。 B<int>::getHelper()使用static InitHelper B<int>::mInitmInit存在。

  • [1]評論,[2]未評論: 它在VS2008中適用於我。

4

問題是你給靜態成員變量的定義也是模板。

template<class T> 
std::string B<T>::mB; 
template<class T> 
typename B<T>::InitHelper B<T>::mInit; 

在編譯期間,這實際上沒有定義任何內容,因爲T是未知的。它類似於類聲明或模板定義,編譯器在看到它時不會生成代碼或保留存儲。

當您使用模板類時,定義會稍後隱式發生。因爲在不存在的情況下,您不使用B < int> :: mInit,它永遠不會被創建。

一個解決辦法是顯式定義所需的部件(不對其進行初始化):地方把源文件

template<> 
typename B<int>::InitHelper B<int>::mInit; 

這個工作原理基本相同的方式顯式定義模板類。

+0

我想,在這個例子中,將初始化放到另一個頭文件中並沒有什麼區別,因爲所有東西都在同一個翻譯單元中。 – 2009-11-30 11:58:42

+0

當然。那就是爲什麼我使用了條件式(「would」)。但我不是英語母語的人,所以這是可以誤導的。我刪了它。 – hirschhornsalz 2009-11-30 15:08:01

+0

那麼你現在明白爲什麼你會得到一個段錯誤/空的成員,或者它還不清楚? – hirschhornsalz 2009-11-30 15:12:03