2009-08-19 179 views
5

我想在加載時註冊一堆類的工廠。我的策略是利用靜態初始化來確保在main()開始之前,工廠已經準備好了。當我動態地鏈接我的庫時,這個策略似乎工作,但是當我靜態鏈接時,這種策略不起作用;當我靜態鏈接時,只有一些靜態數據成員被初始化。爲什麼靜態數據成員不能被初始化?

假設我的工廠生產汽車。我有CarCreator類可以實例化少數汽車,但不是全部。我希望工廠能夠收集所有這些CarCreator類,以便尋找新車的代碼無需知道誰將參與實際施工就可以去工廠。

所以我有

CarTypes.hpp

enum CarTypes 
{ 
    prius = 0, 
    miata, 
    hooptie, 
    n_car_types 
}; 

MyFactory.hpp

class CarCreator 
{ 
public: 
    virtual Car * create_a_car(CarType) = 0; 
    virtual std::list<CarTypes> list_cars_I_create() = 0; 
}; 

class MyFactory // makes cars 
{ 
public: 
    Car * create_car(CarType type); 
    void factory_register(CarCreator *) 

    static MyFactory * get_instance(); // singleton 
private: 
    MyFactory(); 

    std::vector< CarCreator * > car_creator_map; 
}; 

MyFactory.cpp

MyFactory:: MyFactory() : car_creator_map(n_car_types); 

MyFactory * MyFactory::get_instance() { 
    static MyFactory * instance(0); /// Safe singleton 
    if (instance == 0) { 
     instance = new MyFactory; 
    } 
    return instance; 
} 

void MyFactory::factory_register(CarCreator * creator) 
{ 
    std::list<CarTypes> types = creator->list_cars_I_create(); 
    for (std::list<CarTypes>::const_iteator iter = types.begin(); 
     iter != types.end(); ++iter) { 
     car_creator_map[ *iter ] = creator; 
    } 
} 

Car * MyFactory::create_car(CarType type) 
{ 
    if (car_creator_map[ type ] == 0) { // SERIOUS ERROR! 
     exit(); 
    } 
    return car_creator_map[ type ]->create_a_car(type); 
} 

...

然後,我將有具體的汽車和汽車專用創:

Miata.cpp

class Miata : public Car {...}; 

class MiataCreator : public CarCreator { 
public: 
    virtual Car * create_a_car(CarType); 
    virtual std::list<CarTypes> list_cars_I_create(); 
private: 
    static bool register_with_factory(); 
    static bool registered; 
}; 

bool MiataCreator::register_with_factory() 
{ 
    MyFactory::get_instance()->factory_register(new MiataCreator); 
    return true; 
} 

bool MiataCreator::registered(MiataCreator::register_with_factory()); 

...

重申:動態鏈接庫我,MiataCreator ::註冊將得到初始化,靜態鏈接我的庫,它不會被初始化。

使用靜態構建,當有人去工廠請求Miata時,car_creator_map的miata元素將指向NULL,程序將退出。

有什麼特別的私人靜態積分數據成員,他們的初始化將以某種方式跳過?靜態數據成員是否僅在使用類時才初始化?我的CarCreator類不在任何頭文件中聲明;他們完全生活在.cpp文件中。是否有可能編譯器內聯初始化函數,並以某種方式避免調用MyFactory :: factory_register

這個註冊問題有更好的解決方案嗎?

它不是列出IALL的CarCreators在一個單一的功能,與工廠直接註冊各一臺,然後以保證函數被調用的選項。特別是,我想將幾​​個庫鏈接在一起,並在這些單獨的庫中定義CarCreators,但仍然使用單個工廠來構建它們。

...

這裏有一些迴應,我期待,但它並沒有解決我的問題:

1)你的單身廠是不是線程安全的。 a)應該不重要,我只用一個線程工作。

2)你的單身廠可能當你CarCreators被初始化未被初始化(即你有一個靜態初始化失敗) a)通過將單例實例放入一個函數中,我使用了單例類的安全版本。如果這是一個問題,我應該看到輸出,如果我添加打印語句MiataCreator's::register_with_factory方法:我不知道。

+0

另請參閱此併發問題http://stackoverflow.com/questions/1300778/how-to-prevent-the-linker-from-optimizing-away-startup-code – 2009-08-19 15:55:40

+0

我會使用objdump和/或nm(或Windows上的dumpbin)在最終的可執行文件上。有可能代碼甚至沒有包含,因爲鏈接器看到你的程序沒有引用靜態庫中的任何東西。 – nos 2009-08-19 16:01:55

+0

這與靜態或動態鏈接以及靜態成員初始化*順序未確定的事實有很大關係。泰勒麥克亨利的回答是對的。 – quark 2009-08-19 16:35:47

回答

7

我認爲你有一個靜態初始化順序的悲劇,但與工廠。

這並不是說它的註冊標誌沒有被初始化,它只是不夠快就被初始化。

你不能依賴靜態初始化順序除非該程度:在同一個轉換單元中定義(.cpp文件)

  1. 靜態變量將列在定義
  2. 靜態變量的順序初始化在該翻譯單元中的任何功能或方法被首次調用之前,翻譯單元將被初始化。

什麼你不能依靠的是一個靜態變量將一些其他翻譯單元調用首次在一個函數或方法之前被初始化。

特別是,在第一次調用MyFactory :: create_car(在MyFactory.cpp中定義)之前,不能依賴MiataCreator :: registered(在Miata.cpp中定義)進行初始化。像所有未定義的行爲一樣,有時候你會得到你想要的東西,有時你不會,最奇怪的最不看似不相關的東西(例如靜態鏈接和動態鏈接)會改變它是否以你想要的方式工作是否。

您需要做的是爲Miata.cpp中定義的註冊標誌創建靜態訪問方法,並讓MyFactory工廠通過此訪問器獲取值。由於訪問器與變量定義位於同一個翻譯單元中,因此該變量將在訪問器運行時初始化。然後您需要從某處調用該訪問器。

+0

因此,靜態數據成員並不總是在加載時初始化。有什麼是嗎? 我想避免讓MyFactory知道它需要與哪些類進行通信。在我開始實施CarCreators之前,我只在MyFactory中有一個巨大的switch語句: switch(car_type){case} miata:return new Miata; 這個設置的一個大問題是所有的Car類都必須在工廠的同一個庫中定義。隨着圖書館變大,開發時間減慢。 – Andrew 2009-08-19 16:50:56

+0

「2.在翻譯單元中定義的靜態變量將在該翻譯單元中的任何函數或方法第一次被調用之前被初始化。」有一個雞和雞蛋的問題:翻譯單位需要調用的知識存在於翻譯單元中。那麼,如果沒有在集中的地方(例如Factory.cpp中)對該指令進行硬編碼,就沒有辦法溝通「嘿,使用這些代碼」?這非常令人沮喪。 – Andrew 2009-08-19 17:09:18

+0

多數民衆贊成在理論,但即使提振球員依靠靜態初始化之前主http://www.boost.org/doc/libs/1_39_0/libs/flyweight/doc/tutorial/technical.html – 2009-08-19 17:58:21

3

如果使用靜態鏈接,則意味着將所有對象文件(.o)添加到二進制文件,這應該像動態文件一樣工作,如果製作了(.a)靜態庫,鏈接器不會將它們鏈接到內部在靜態庫內使用的對象是鏈接的,在這種情況下沒有明確使用。

所有自動註冊技術都依賴於加載時代碼及其避免靜態失敗的方式,例如創建對象並按需返回的函數。

但是,如果你不能設法加載永遠不會工作,鏈接目標文件一起工作,並加載動態庫,但靜態庫永遠不會鏈接沒有明確的依賴關係。

0

你什麼時候檢查miata元素是否在地圖內?它在主要之前還是之後?
我能想到的唯一原因是訪問main()之前的地圖元素(例如在全局初始化中),這可能發生在MiataCreator :: registered創建之前(如果它位於不同的翻譯單元中)

+0

我只在main()開始後才從地圖上讀取。 – Andrew 2009-08-19 16:51:54

1

通常對於靜態庫,鏈接器只會從主程序引用的庫中提取.o文件。由於您沒有參考MiataCreator :: registered或者Miata.cpp中的任何內容,但依賴於靜態啓動,如果鏈接器從靜態庫鏈接,則甚至不會將該代碼包含在exe中 -

檢查生成的可執行文件與nm或objdump(或如果你在Windows上的dumpbin)MiataCreator :: registered的代碼是否實際包含在exe文件中,當你靜態鏈接時。

我不知道如何強制鏈接到每一個位,雖然靜態庫的作品包括..

+0

是的,那是我在想什麼。 – 2009-08-19 21:51:10

+0

這只是解釋的一半。鏈接器的作用就像一個垃圾回收器:它探索所有指針(函數調用),從一些活動的(main)開始。如果在探索活動內存(從main()可能到達的函數)期間永遠不會到達對象(編譯單元永遠不會到達),那麼垃圾回收器會刪除它(鏈接器不包含它)。 (我會在我的下一個評論中繼續這個) – Andrew 2009-08-20 13:50:15

+0

向鏈接器發信號通知Miata.cpp應該包含的唯一方法是擁有一些中心函數(例如,位於MyFactroy.cpp中的函數)包含對某段Miata.cpp中的數據我的觀點是,這個額外的步驟與原始問題一樣嚴格:MyFactory(或中央人員)必須知道所有CarCreators。沒有解決方案,MyFactory可以等待,直到加載時間爲未知的CarCreators來註冊請求。我無法將CarCreators放入其他庫中。 – Andrew 2009-08-20 13:51:48

0

就我個人而言,我認爲你正在休息鏈接器的犯規。

布爾變量沒有被使用'bool MiataCreator :: registered'os連接器沒有將它們從lib中拉到可執行文件中(記住,如果在可執行文件中沒有引用函數/全局變量,鏈接器將不會從lib中取出它們的對象[它僅查找當前在可執行文件中未定義的對象])

您可以在'bool MiataCreator :: register_with_factory()'中添加一些打印語句以查看它是否被調用。或者檢查可執行文件中的符號以確認它在那裏。

有些事情,我會做:

// Return the factory by reference rather than pointer. 
// If you return by pointer the user has to assume it could be NULL 
// Also the way you were creating the factory the destructor was never 
// being called (though not probably a big deal here) so there was no 
// cleanup, which may be usefull in the future. And its just neater. 
MyFactory& MyFactory::get_instance() 
{ 
    static MyFactory instance; /// Safe singleton 
    return instance; 
} 

不是有對象的兩步初始化。我懷疑由於鏈接器而失敗。創建工廠的一個實例並獲取構造函數進行註冊。

bool MiataCreator::register_with_factory() 
{ 
    MyFactory::get_instance()->factory_register(new MiataCreator); 
    return true; 
} 
// 
// I would hope that the linker does not optimize this out (but you should check). 
// But the linker only pulls from (or searches in) static libraries 
// for references that are explicitly not defined. 
bool MiataCreator::registered(MiataCreator::register_with_factory()); 

我這樣做:

MiataCreator::MiataCreator() 
{ 
    // I would change factory_register to take a reference. 
    // Though I would store it internall as a pointer in a vector. 
    MyFactory::getInstance().factory_register(*this); 
} 

// In Cpp file. 
static MiataCreator factory; 

鏈接器知道C++對象和構造函數,應該把所有的全局變量的構造有可能有副作用的影響(我知道你的布爾做的好,但我可以看到一些鏈接器優化出來)。

無論如何,這是我的2c值得。

0

使用gcc,您可以添加-Wl,--whole-archive myLib.a --Wl,--no-whole-archive。這將強制鏈接器包含即使不被引用的對象。但是,這不是便攜式的。