應用設計形式中的單例形式來完成C++的boost庫。本站提示廣大學習愛好者:(應用設計形式中的單例形式來完成C++的boost庫)文章只能為提供參考,不一定能成為您想要的結果。以下是應用設計形式中的單例形式來完成C++的boost庫正文
線程平安的單例形式
1、懶漢形式:即第一次挪用該類實例的時刻才發生一個新的該類實例,並在今後僅前往此實例。
須要用鎖,來包管其線程平安性:緣由:多個線程能夠進入斷定能否曾經存在實例的if語句,從而non thread safety。
應用double-check來包管thread safety。然則假如處置年夜量數據時,該鎖才成為嚴重的機能瓶頸。
1、靜態成員實例的懶漢形式:
class Singleton { private: static Singleton* m_instance; Singleton(){} public: static Singleton* getInstance(); }; Singleton* Singleton::getInstance() { if(NULL == m_instance) { Lock(); //借用其它類來完成,如boost if(NULL == m_instance) { m_instance = new Singleton; } UnLock(); } return m_instance; }
2、外部靜態實例的懶漢形式
這裡須要留意的是,C++0X今後,請求編譯器包管外部靜態變量的線程平安性,可以不加鎖。但C++ 0X之前,仍須要加鎖。
class SingletonInside { private: SingletonInside(){} public: static SingletonInside* getInstance() { Lock(); // not needed after C++0x static SingletonInside instance; UnLock(); // not needed after C++0x return instance; } };
2、餓漢形式:即不管能否挪用該類的實例,在法式開端時就會發生一個該類的實例,並在今後僅前往此實例。
由靜態初始化實例包管其線程平安性,WHY?由於靜態實例初始化在法式開端時進入主函數之前就由主線程以單線程方法完成了初始化,不用擔憂多線程成績。
故在機能需求較高時,應應用這類形式,防止頻仍的鎖爭取。
class SingletonStatic { private: static const SingletonStatic* m_instance; SingletonStatic(){} public: static const SingletonStatic* getInstance() { return m_instance; } }; //內部初始化 before invoke main const SingletonStatic* SingletonStatic::m_instance = new SingletonStatic;
boost庫的完成示例
單例原來是個很簡略的形式,完成上應當也是很簡略,但C++單例的簡略完成會有一些坑,有了下面線程平安的基本,上面來看看為了不這些坑如何一步步演變到boost庫的完成方法。
計劃一
class QMManager { public: static QMManager &instance() { static QMManager instance_; return instance_; } }
這是最簡略的版本,在單線程下(或許是C++0X下)是沒任何成績的,但在多線程下就不可了,由於static QMManager instance_;這句話不是線程平安的。
在部分感化域下的靜態變量在編譯時,編譯器會創立一個附加變量標識靜態變量能否被初始化,會被編譯器釀成像上面如許(偽代碼):
static QMManager &instance() { static bool constructed = false; static uninitialized QMManager instance_; if (!constructed) { constructed = true; new(&s) QMManager; //construct it } return instance_; }
這裡有競爭前提,兩個線程同時挪用instance()時,一個線程運轉到if語句進入後還沒設constructed值,此時切換到另外一線程,constructed值照樣false,異樣進入到if語句裡初始化變量,兩個線程都履行了這個單例類的初始化,就不再是單例了。
計劃二
一個處理辦法是加鎖:
static QMManager &instance() { Lock(); //鎖本身完成 static QMManager instance_; UnLock(); return instance_; }
但如許每次挪用instance()都要加鎖解鎖,價值略年夜。
計劃三
那再轉變一下,把外部靜態實例釀成類的靜態成員,在內部初始化,也就是在include了文件,main函數履行前就初始化這個實例,就不會有線程重入成績了:
class QMManager { protected: static QMManager instance_; QMManager(); ~QMManager(){}; public: static QMManager *instance() { return &instance_; } void do_something(); }; QMManager QMManager::instance_; //內部初始化
這被稱為餓漢形式,法式一加載就初始化,不論有無挪用到。
看似沒成績,但照樣有坑,在一個2B情形下會有成績:在這個單例類的結構函數裡挪用另外一個單例類的辦法能夠會有成績。
看例子:
//.h class QMManager { protected: static QMManager instance_; QMManager(); ~QMManager(){}; public: static QMManager *instance() { return &instance_; } }; class QMSqlite { protected: static QMSqlite instance_; QMSqlite(); ~QMSqlite(){}; public: static QMSqlite *instance() { return &instance_; } void do_something(); }; QMManager QMManager::instance_; QMSqlite QMSqlite::instance_; //.cpp QMManager::QMManager() { printf("QMManager constructor\n"); QMSqlite::instance()->do_something(); } QMSqlite::QMSqlite() { printf("QMSqlite constructor\n"); } void QMSqlite::do_something() { printf("QMSqlite do_something\n"); }
這裡QMManager的結構函數挪用了QMSqlite的instance函數,但此時QMSqlite::instance_能夠還沒有初始化。
這裡的履行流程:法式開端後,在履行main前,履行到QMManager QMManager::instance_;這句代碼,初始化QMManager裡的instance_靜態變量,挪用到QMManager的結構函數,在結構函數裡挪用QMSqlite::instance(),取QMSqlite裡的instance_靜態變量,但此時QMSqlite::instance_還沒初始化,成績就湧現了。
那這裡會crash嗎,測試成果是不會,這應當跟編譯器有關,靜態數據區空間應當是先被分派了,在挪用QMManager結構函數前,QMSqlite成員函數在內存裡曾經存在了,只是還未調到它的結構函數,所以輸入是如許:
QMManager constructor QMSqlite do_something QMSqlite constructor
計劃四
那這個成績怎樣處理呢,單例對象作為靜態部分變量有線程平安成績,作為類靜態全局變量在一開端初始化,有以上2B成績,那聯合下上述兩種方法,可以處理這兩個成績。boost的完成方法是:單例對象作為靜態部分變量,但增長一個幫助類讓單例對象可以在一開端就初始化。以下:
//.h class QMManager { protected: struct object_creator { object_creator() { QMManager::instance(); } inline void do_nothing() const {} }; static object_creator create_object_; QMManager(); ~QMManager(){}; public: static QMManager *instance() { static QMManager instance; return &instance; } }; QMManager::object_creator QMManager::create_object_; class QMSqlite { protected: QMSqlite(); ~QMSqlite(){}; struct object_creator { object_creator() { QMSqlite::instance(); } inline void do_nothing() const {} }; static object_creator create_object_; public: static QMSqlite *instance() { static QMSqlite instance; return &instance; } void do_something(); }; QMManager::object_creator QMManager::create_object_; QMSqlite::object_creator QMSqlite::create_object_;
聯合計劃3的.cpp,這下可以看到准確的輸入和挪用了:
QMManager constructor QMSqlite constructor QMSqlite do_something
來看看這裡的履行流程:
初始化QMManager類全局靜態變量create_object_
->挪用object_creator的結構函數
->挪用QMManager::instance()辦法初始化單例
->履行QMManager的結構函數
->挪用QMSqlite::instance()
->初始化部分靜態變量QMSqlite instance
->履行QMSqlite的結構函數,然後前往這個單例。
跟計劃三的差別在於QMManager挪用QMSqlite單例時,計劃3是取到全局靜態變量,此時這個變量未初始化,而計劃四的單例是靜態部分變量,此時挪用會初始化。
跟最後計劃一的差別是在main函數前就初始化了單例,不會有線程平安成績。
終究boost
下面為了解釋清晰點去除模版,現實應用是用模版,不消寫那末多反復代碼,這是boost庫的模板完成:
template <typename T> struct Singleton { struct object_creator { object_creator(){ Singleton<T>::instance(); } inline void do_nothing()const {} }; static object_creator create_object; public: typedef T object_type; static object_type& instance() { static object_type obj; //聽說這個do_nothing是確保create_object結構函數被挪用 //這跟模板的編譯有關 create_object.do_nothing(); return obj; } }; template <typename T> typename Singleton<T>::object_creator Singleton<T>::create_object; class QMManager { protected: QMManager(); ~QMManager(){}; friend class Singleton<QMManager>; public: void do_something(){}; }; int main() { Singleton<QMManager>::instance()->do_something(); return 0; }
其實Boost庫如許的完成像打了幾個補釘,用了一些奇技淫巧,固然確切繞過了坑完成了需求,但感到挺欠好的。