VC++ 2017的一個單例模板類

blowfish發表於2017-11-09
一百個人有一百種單例的寫法,C++中輪子總是被重複造。

我目前的需求是:
1、用於VC2017,EXE/DLL都能用。
2、單例。當然,這隱含意思是其構造得是執行緒安全的。
3、只初始化一次。在構造之外進行的額外初始化操作,只執行一次,無論成功還是失敗。
4、能析構。如果leak,不釋放資源,怎麼都覺得不夠優雅。需要注意的是,如果在DLL中,單例的析構是在DLL_PROCESS_DETACH階段執行的,此時是持有ntdll的loader鎖的,很多操作都容易導致卡死。所以不要把複雜的操作放在析構階段。
5、自己實現執行緒安全的構造、初始化。modern c++的static物件的構造預設就是執行緒安全的,為什麼還要多此一舉呢? 是因為微軟的DLL loader在XP/2003上有(大)bug:如果用LoadLibrary()去load一個DLL時,DLL loader居然沒初始化這個DLL的執行緒區域性儲存,而這個DLL恰好是用/Zc:threadSafeInit+編譯的話,編譯器生成的程式碼會一上來就直接引用並未被初始化的執行緒區域性儲存,導致崩掉。 為了能支援 XP/2003,就只能自己實現static物件的執行緒安全構造了。為此要增加編譯命令列引數/Zc:threadSafeInit-來禁用編譯器自己的執行緒安全構造。
6、方便繼承。儘可能不用開銷較大的虛擬函式。用模板。

關於單例的實現,最經典的應該是Scott Meyers和Andrei Alexandrescu寫的《C++ and the Perils of Double-Checked Locking》(http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf)。DCLP(Double-Checked Locking Pattern)能保證多執行緒下的高效率。為了更好地支援用DCLP,VC++甚至增加了/volatile:ms的編譯器私家擴充套件,使得volatile關鍵字預設就具有acquire/release語義。但是有了modern c++的memory model,就可以用/volatile:iso關掉這個特性,只依賴於 modern c++的memory model來實現DCLP。

下面是一個單例模板類的實現。使用方法是:
1、在stdafx.h中包含如下標頭檔案:
#include <mutex>
#include <atomic>
2、 從本模板類派生出子類。
3、 在子類中實現public函式InitOnce()。

例子:
class CSingletonExample: public CSingletonT<CSingletonExample>
{
	public:
		CSingletonExample() {}
		~CSingletonExample() {}

		bool InitOnce(void)
		{
			return true;
		}

	private:
		friend class CSingletonT<CSingletonExample>;
};

CSingletonExample::Instance().Init();

單例模板類:

template<typename T>
class CSingletonT
{
	public:

		static T *InstancePtr()
		{
			T * pInstance = s_Instance.load(std::memory_order_acquire);
			if (pInstance)
				return pInstance;

			std::lock_guard<std::mutex> CreateLock(s_Mutex);
			pInstance = s_Instance.load(std::memory_order_relaxed);
			if (pInstance)
				return pInstance;

			static T staticObj;

			pInstance = &staticObj;
			s_Instance.store(pInstance, std::memory_order_release);

			return pInstance;
		}

		inline static T & Instance()
		{
			return *InstancePtr();
		}

		CSingletonT() 
			:m_InitState(SINGLETON_INIT_UNKNOWN)
		{
		}

		~CSingletonT()
		{
			#ifdef _DEBUG
				s_Instance.store(NULL, std::memory_order_relaxed);
				m_InitState.store(SINGLETON_INIT_UNKNOWN, std::memory_order_relaxed);
			#endif
		}

		/*
			此處所呼叫到的public函式InitOnce()需要子類自己實現,原型是:
				bool InitOnce(void);
		*/
		bool Init(void)
		{
			SINGLETON_INIT_STATE InitState = m_InitState.load(std::memory_order_acquire);
			if (InitState != SINGLETON_INIT_UNKNOWN)
				return InitState == SINGLETON_INIT_SUCCESS;

			std::lock_guard<std::mutex> InitLock(s_Mutex);
			InitState = m_InitState.load(std::memory_order_relaxed);
			if (InitState != SINGLETON_INIT_UNKNOWN)
				return InitState == SINGLETON_INIT_SUCCESS;

			InitState = (static_cast<T *>(this)->InitOnce() ? SINGLETON_INIT_SUCCESS : SINGLETON_INIT_FAILED);
			m_InitState.store(InitState, std::memory_order_release);

			return InitState == SINGLETON_INIT_SUCCESS;
		}

		CSingletonT(const CSingletonT&) = delete;
		CSingletonT& operator=(const CSingletonT&) = delete;

	private:

		//單例的初始化狀態,為三態。
		enum SINGLETON_INIT_STATE
		{
			SINGLETON_INIT_UNKNOWN,
			SINGLETON_INIT_SUCCESS,
			SINGLETON_INIT_FAILED,
		};

		std::atomic<SINGLETON_INIT_STATE> m_InitState;

		static std::atomic<T *> s_Instance;
		static std::mutex       s_Mutex;
};

template<typename T>
__declspec(selectany) std::atomic<T *> CSingletonT<T>::s_Instance;

template<typename T>
__declspec(selectany) std::mutex  CSingletonT<T>::s_Mutex;


相關文章