6

靜態STL容器的雙初始化周圍有「靜態變量初始化順序的悲劇」了幾個很好的問題和答案在這裏,但我似乎已經撞到它的另一個表現,特別難看,因爲它不會崩潰但會泄漏並泄露數據。在C++庫

我有一個自定義C++庫和一個鏈接它的應用程序。庫中有一個靜態STL容器,用於註冊一個類的所有實例。這些實例恰好是應用程序中的靜態變量。由於「慘敗」(我相信),我們在應用程序初始化過程中讓容器充滿應用程序實例,然後庫進行初始化並重置容器(可能會泄漏內存),最終只會結束來自圖書館的實例。

這是我如何與簡化的代碼複製它:

mylib.hpp:

#include <iostream> 
#include <string> 
#include <vector> 

using namespace std; 

class MyLibClass { 
    static vector<string> registry; 
    string myname; 
    public: 
    MyLibClass(string name); 
}; 

mylib.cpp:

#include "mylib.hpp" 

vector<string> MyLibClass::registry; 

MyLibClass::MyLibClass(string name) 
: myname(name) 
{ 
    registry.push_back(name); 
    for(unsigned i=0; i<registry.size(); i++) 
     cout << " ["<< i <<"]=" << registry[i]; 
    cout << endl; 
} 

MyLibClass l1("mylib1"); 
MyLibClass l2("mylib2"); 
MyLibClass l3("mylib3"); 

MyApp.cpp中:

#include "mylib.hpp" 

MyLibClass a1("app1"); 
MyLibClass a2("app2"); 
MyLibClass a3("app3"); 

int main() { 
    cout << "main():" << endl; 
    MyLibClass m("main"); 
} 

編譯與對象:

g++ -Wall -c myapp.cpp mylib.cpp 
g++ myapp.o mylib.o -o myapp1 
g++ mylib.o myapp.o -o myapp2 

運行myapp1:

$ ./myapp1 
[0]=mylib1 
[0]=mylib1 [1]=mylib2 
[0]=mylib1 [1]=mylib2 [2]=mylib3 
[0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 
[0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2 
[0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2 [5]=app3 
main(): 
[0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2 [5]=app3 [6]=main 

運行myapp2:

$ ./myapp2 
[0]=app1 
[0]=app1 [1]=app2 
[0]=app1 [1]=app2 [2]=app3 
[0]=mylib1 
[0]=mylib1 [1]=mylib2 
[0]=mylib1 [1]=mylib2 [2]=mylib3 
main(): 
[0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=main 

問題來了,靜態載體重新初始化,還是在初始化之前使用?這是預期的行爲嗎?

如果我AR「圖書館作爲「mylib.a上」(AR RCS mylib.a上mylib.o),該問題不會發生,但可能是因爲只有一個有效的以鏈接到.A這是通過讓圖書館在最後一個地方,如myapp1在這裏。

但是在我們真正的應用程序中,一個包含許多目標文件和一些靜態庫(.a)的更復雜的庫共享一些靜態註冊表,問題正在發生,我們目前設法解決它的唯一方法是申請'[10.15] How do I prevent the "static initialization order fiasco"?'

(我仍然在研究中我們有些複雜的編譯系統,看看我們是否已正確連接)。

+1

非常好的示例代碼。我希望所有問題都是這樣的。 – 2011-03-24 17:14:54

回答

4

解決初始化順序問題的一種方法是將靜態變量從全局範圍移動到本地範圍。

取而代之的是類中有registry變量,把它變成一個功能:

vector<string> & MyLibClass::GetRegistry() 
{ 
    static vector<string> registry; 
    return registry; 
} 

在你會直接使用registry的地方,有它調用GetRegistry

+0

我選擇這個答案是因爲它提供了清晰的示例代碼。如上所述,在研究之後,它似乎也是邁耶的。 – gatopeich 2011-03-28 19:08:00

+0

@gatopeich,是的,當我看到其他答案後,我查了一下,但我從來不知道它有一個名字。 – 2011-03-29 02:35:45

2

如果你給vector<string>一個自定義的構造函數,你會看到,它確實只調用一次,但在myapp2您使用registry未初始化的第一,那麼它被初始化(「刪除」一切的內),然後再次填。它不段錯誤只是運氣:)

我不能告訴標準的一部分了一些關於這種行爲,但恕我直言,你應該/絕不/讓靜態變量是互相依賴的。例如,您可以使用Meyers單例作爲註冊表。

+0

邁爾斯單身人士+1 +1 – wheaties 2011-03-24 17:07:53

+0

確實,這是我們選擇的解決方案,但我不知道它是「邁爾的」。 – gatopeich 2011-03-28 19:05:51

+0

Scott Meyers是這個名字。我認爲他是第一個在他的「更有效的C++」中提出這個單例實現的人。 – filmor 2011-03-29 10:53:36

0

您正在使用2種已知技術。

(1) 「模塊/庫/命名空間」 爲 「設備」 圖案

(2)自定義類型的註冊,與一個靜態類。

用「Object Pascal」和「Plain C」做了類似的事情。我有幾個文件,每個文件作爲一個模塊/命名空間,與typedef,類,函數。另外,每個「名稱空間」有兩種特殊方法(相同的簽名或原型),模擬連接設備和斷開設備。已經嘗試自動調用這些方法,但執行順序也出錯了。

Static,Singleton類可能會變得一團糟。我建議,忘記使用宏或預處理器/編譯器和自己調用你的初始化/終結方法

---- 
mylib.hpp 
---- 

class MyLibClass { 
    public: 
    Register(string libraryName); 
    UnRegister(string libraryName); 
}; 

// don't execute the "custom type registration here" 

----- 
mynamespace01.cpp 
----- 
#include "mylib.hpp" 

void mynamespace01_otherstuff() { ... } 

// don't execute registration 
void mynamespace01_start() { 
    if not (MyLibClass::IsUnRegistered("mynamespace01")) MyLibClass::Register("mynamespace01"); 
} 

void mynamespace01_finish() 
{ 
    if not (MyLibClass::IsRegistered("mynamespace01")) MyLibClass::UnRegister("mynamespace01"); 
} 

----- 
mynamespace02.cpp 
----- 
#include "mylib.hpp" 

// check, "2" uses "1" !!! 
#include "mynamespace01.hpp" 

void mynamespace02_otherstuff() { ... } 

// don't execute registration !!! 
void mynamespace02_start() { 
    // check, "2" uses "1" !!! 
    void mynamespace01_start(); 

    if not (MyLibClass::IsUnRegistered("mynamespace01")) MyLibClass::Register("mynamespace02"); 

    void mynamespace02_start(); 
} 

void mynamespace02_finish(){ 
    void mynamespace02_finish(); 

    if not (MyLibClass::IsRegistered("mynamespace02")) MyLibClass::UnRegister("mynamespace02"); 

    // check, "2" uses "1" !!! 
    void mynamespace02_start(); 
} 

----- 
myprogram.cpp 
----- 

#include "mynamespace01.hpp" 
#include "mynamespace02.hpp" 

void myprogram_otherstuff() { ... } 

// don't execute registration !!! 
void myprogram_start() { 
    // check, "2" uses "1" !!! 
    mynamespace01_start(); 
    mynamespace02_start(); 

    if not (MyLibClass::IsUnRegistered("myprogram")) MyLibClass::Register("myprogram"); 
} 
void myprogram_finish() { 
    if not (MyLibClass::IsRegistered("myprogram")) MyLibClass::UnRegister("myprogram"); 

    // check, "2" uses "1" !!! 
    mynamespace01_finish(); 
    mynamespace02_finish(); 
} 

void main() { 
    // all registration goes here !!!: 

    // "device" initializers order coded by hand: 
    myprogram_start(); 

    // other code; 

    // "device" finalizers order inverse coded by hand: 
    myprogram_finish(); 
} 
----- 

確認這個代碼是更爲複雜和冗長的是你的,但 ,以我的經驗,更穩定。

我還將「終結器」添加到「初始化器」,並替換「登記」的標識符。

祝你好運。