2014-09-26 178 views
1

這裏賽格故障代碼,簡化...訪問單身般的靜態成員

//A class used in the next class, Nothing much to worry about 
class BasicLogger{ 
//... 
}; 

下面是我的主類。它有兩個你需要看的成員:它自己類型的一個靜態成員(稱爲log)。 而且,一個容器(稱爲repo)用於保存上述類的對象。 repo的項目,採用訪問operator[]超載:

class Logger { 
protected: 
    // repository of profilers. each profiler is distinguished by a file name! 
    std::map<const std::string, boost::shared_ptr<BasicLogger> > repo; 
public: 
    Logger(){} //breakpoints never reach here. why? 
    //universal singleton-like access to this class 
    static Logger log; 
    //returns a member stored in the above 'repo' 
    virtual BasicLogger & operator[](const std::string &key); 
}; 

問題來源於此方法:

BasicLogger & Logger::operator[](const std::string &key) 
{ 
    std::map<std::string, boost::shared_ptr<BasicLogger> >::iterator it = repo.find(key); 
    if(it == repo.end()){ 
     std::cout << "creating a new Logger for " << key << std::endl; 
     boost::shared_ptr<BasicLogger> t(new LogEngine(key)); 
     std::map<const std::string, boost::shared_ptr<BasicLogger> > repo_debug;//just for debug 
     repo_debug.insert(std::make_pair(key,t));//ok 
     repo.insert(std::make_pair(key,t));//seg fault 
     return *t; 
    } 
    return *it->second; 
} 

和最後一條信息:在整個項目中,repo容器項目像下面訪問。

namespace{ 
BasicLogger & logger = Logger::log["path_set"]; 
} 

問題:

的問題是,在節目的開始,任何事情之前,控制直接進入BasicLogger & logger = Logger::log["path_set"];

Q1:到底爲什麼不控制走在這裏第一次?僅僅是因爲log是靜態的還是匿名的命名空間最初也會出現?

反正, 所以當運算符[]執行時,repo似乎是未初始化的。我添加了一個與repo具有相同簽名的本地虛擬變量(repo_debug)。並且觀察到它們的值用gdb:

//local repo_debug 
    Details:{... _M_header = {... _M_parent = 0x0, _M_left = 0x7fffffffdc08, _M_right = 0x7fffffffdc08}, _M_node_count = 0}}} 
//main 'repo' 
    Details:{..._M_parent = 0x0, _M_left = 0x0, _M_right = 0x0}, _M_node_count = 0}}} 

Q2。爲什麼repo未初始化?基本上,爲什麼Logger的構造函數沒有被調用?

Q3。建議照顧這個問題是高度讚賞。 謝謝

+1

通常你只會問只有一個問題(如果他們是嚴格相關的話可能會好起來) – 2014-09-26 08:22:53

+3

我最初的看法:你的靜態和全局正在比賽初始化。猜猜誰輸了。 – WhozCraig 2014-09-26 08:26:12

+0

Logger :: log'在哪裏實例化,以及所有這些'logger = ...'?相同的編譯單元?不同的編譯單元?在第二種情況下,您遇到了麻煩,初始化順序未定義。如果你想以這種方式使用它,創建一些吸氣劑(吸氣劑將首先檢查並創建它,可能使用原子)。或者在一個文件中實例化所有的記錄器。 – firda 2014-09-26 08:32:53

回答

1

問題1:假定聲明是在不同的編譯單元中。編譯單元之間的靜態初始化順序是實現定義的。所以,這是偶然的。我會說幸運的機會,因爲另一種方式,你最初會認爲它只是在後來發現它在另一個CPU /編譯器/操作系統上崩潰。

問題2:由於匿名命名空間中的logger被初始化,導致segfault阻止靜態初始化log

Q3。您可以通過避免設計中的單例來避免此問題。但是如果你想單身,避免靜態初始化順序的悲劇的一種方式是構建在第一次使用成語:

Logger& Logger::log() { 
    static Logger* log = new Logger(); 
    return *log; 
} 

的缺點是動態分配的對象永遠不會真正釋放(單身人士將在該月底被釋放程序無論如何,但可能是一個問題,如果你沒有一個操作系統運行)

靜態本地化的初始化的線程安全性由§6中的標準保證。7/4(C++ 11草案):

...否則這樣一個變量是 第一次控制通過它的聲明初始化;這樣的變量在初始化完成時被認爲初始化。如果通過拋出異常退出初始化,則初始化 未完成,因此下次控制進入聲明時將再次嘗試初始化。 如果控制在變量初始化時同時輸入 聲明,則併發執行應等待 完成初始化。

在早期的標準,並在Visual C++中,你可以通過確保log被稱爲是靜態初始化(主程序可以生成任何線程之前,其發生)時調用至少有一個構造避免併發問題。

+0

這種方法是線程安全的嗎?我指的是通過本地靜態創建單例。 – firda 2014-09-26 08:40:37

+0

只要構造函數本身是線程安全的。 – user2079303 2014-09-26 08:47:50

+0

那麼,這個本地靜態內部使用了一些原子?當多個線程同時請求記錄器時,它不能被多次創建? – firda 2014-09-26 08:54:03