再談單例模式
阿新 • • 發佈:2018-11-10
之前寫過一篇部落格 《C++單例模式的模板基類》 http://blog.csdn.net/kentzhang_/article/details/48206821
但是,最近才發現實際 上 static T* Instance() 的實現是有一點bug 的,下面分析。
static T *Instance() { if(NULL != m_Instance) { return m_Instance; } m_Mutex.Lock(); if(NULL != m_Instance) { m_Mutex.UnLock(); return m_Instance; } m_Instance = new(std::nothrow) T(); m_Mutex.UnLock(); return m_Instance; }
這種實現在多執行緒環境下,可能會有bug ,原因就在 m_Instance = new (std::nothrow) T() 這行程式碼。
這句程式碼實際上分三步執行:
1、分配記憶體
2、呼叫建構函式,初始化記憶體空間
3、將記憶體地址賦值給單例指標
程式碼可轉化為如下形式:
T* p = newMemory(sizeof(T))
p->BaseSingleton::BaseSingleton()
m_Instance = p
編譯器出於優化的目的,可能會將第一步和第三步合併,變成如下:
m_Instance = newMemory(sizeof(T))
m_Instance->BaseSingleton::BaseSingleton()
那麼在執完第一行程式碼時,有可能會切換到其他執行緒,其他執行緒如果呼叫 Instance()函式,由於m_Insatnce已經賦值,將返回這個指標,但是
指標指向的記憶體並未初始化,所以會造成不可預知的錯誤。
在Boost庫中,有一種很好的單例實現模式,我做了一點修改,就是引用改成指標。程式碼如下:
#ifndef BASESINGLETON_H #define BASESINGLETON_H /** 單例模式採用boost原始碼 由於boost單例返回的是引用,下面改成指標 */ template<class T> class BaseSingleton { public: BaseSingleton(){} private: struct object_creator { // This constructor does nothing more than ensure that instance() // is called before main() begins, thus creating the static // T object before multithreading race issues can come up. object_creator() { BaseSingleton<T>::Instance(); } inline void do_nothing() const { } }; static object_creator create_object; //BaseSingleton(); public: typedef T object_type; // If, at any point (in user code), BaseSingleton<T>::instance() // is called, then the following function is instantiated. static object_type* Instance() { // This is the object that we return a reference to. // It is guaranteed to be created before main() begins because of // the next line. static object_type obj; // The following line does nothing else than force the instantiation // of BaseSingleton<T>::create_object, whose constructor is // called before main() begins. create_object.do_nothing(); return &obj; } }; template <class T> typename BaseSingleton<T>::object_creator BaseSingleton<T>::create_object; #endif // BASESINGLETON_H
我之前實現的單例模式中,必須是第一次手動呼叫Instance()才生成單例物件,這樣就引入了多執行緒的問題。
而Boost實現方式,在單例類中定義一個內嵌的靜態的結構體,這個結構體生成時呼叫自己的建構函式,建構函式中執行Instance()函式,由於單例類內的靜態的結構體生成時,是在main()執行之前,所以巧妙地繞開了多執行緒的問題。