1. 程式人生 > >(C++)用模板或巨集實現單例模式

(C++)用模板或巨集實現單例模式

最近在cocos2d-x開發中經常會用到單例模式,而每個單例模式類實際上具備相似的基礎結構,為了便於快速寫出一個具有單例模式的類,可以藉助模板或者巨集。

1.單例模式的類實現

首先,給出一個基本的實現單例模式的程式碼:

class Singleton
{
public:
	inline static Singleton& GetInstance()
	{
		static Singleton s_instance;
		return s_instance;
	}

private:
	Singleton();
	Singleton(Singleton const&){};
	Singleton& operator= (Singleton const&){return *this;};
	~Singleton();
};
#define SGL Singleton::GetInstance()
因為是用於cocos2d-x下的開發,而手機遊戲很多時候僅需要單執行緒就足夠了,因此此處沒有考慮執行緒安全問題,如果有執行緒安全方面的考慮,則對此類稍加改動即可。另外GetInstance函式沒有返回一個指標而是返回引用,是希望使用者像使用一個物件一樣使用此類,而且這樣的寫法對於改寫成模板或者巨集都更容易,程式碼也更簡潔。為了使用方便,為獲取單例的函式寫了一個巨集,當然這個是可選項,不寫也無所謂。

建構函式、拷貝建構函式、賦值操作符與解構函式全部設為私有,以防止類的使用者不小心對類重新建立和賦值,保證類有且僅有一個例項。

在單例模式中,拷貝建構函式與賦值操作符因為完全沒有用,因此此處直接給出了空實現"{}",而建構函式和解構函式此處並未給出空實現,因為它們往往需要根據具體情況給出具體實現,而且建構函式和解構函式並不適合成為inline。

2.單例模式的模板實現

用模板實現單例模式實際上與基本類實現程式碼大同小異:

#ifndef SINGLETON_H_
#define SINGLETON_H_

template<typename T>
class Singleton
{
public:
	inline static T& GetInstance()
	{
		static T s_instance;
		return s_instance;
	}

private:
	Singleton();
	Singleton(Singleton const&);
	Singleton& operator= (Singleton const&);
	~Singleton();
};
#endif//SINGLETON_H_

如上,大部分程式碼與普通的類實現沒有區別。不過需要注意的是,除了多了一個template宣告式之外,還有一個在文章中難以體現的區別是這種實現方式可能不需要cpp檔案,尤其如果你和我一樣用它做cocos2d-x開發的話,也不需要給出4個私有函式的實現。

使用模板時則類似於這樣
#include "Singleton.h"
class Example
{

};
typedef Singleton<Example> SglExample;
#define SGL_EXAMPLE SglExample::GetInstance()

將需要成為單例模式的類嵌入模板即可,而Example類的程式碼與普通類沒有任何區別。

示例程式碼的最後多出了一個類別名(typedef語句)和一個巨集(#define),作用是方便使用者使用單例,而不需要寫重複程式碼,尤其是模板風格程式碼--實在算不上優美。

需要注意的是,這裡的實現思路是將Example類作為了Singleton模板的“實現素材”,真正的單例是Singleton<Example>而不是Example,關於此設計的缺點,會在稍後詳述。

3.單例模式的巨集實現

巨集實現的程式碼實際上與類實現的程式碼依舊思路相同:

#ifndef SINGLETON_DIFINE_H_
#define SINGLETON_DIFINE_H_

//單例模式巨集
#define SINGLETON(_CLASS_)					\
public:										\
	inline static _CLASS_& GetInstance()	\
	{										\
	static _CLASS_ s_instance;				\
	return s_instance;						\
	}										\
private:									\
	_CLASS_();								\
	_CLASS_(_CLASS_ const&){}				\
	_CLASS_& operator= (_CLASS_ const&){return *this;}	\
	~_CLASS_();								\

//單例模式預設建構函式與解構函式(配合單例模式巨集使用)
#define SINGLETON_C_D(_CLASS_)		\
	_CLASS_::_CLASS_(){}			\
	_CLASS_::~_CLASS_(){}			\

#endif //SINGLETON_DIFINE_H_

巨集的實現與模板實現具有一些相似的特徵,比如只需要.h檔案。但又有很多區別,比如在巨集定義中需要給出拷貝建構函式和賦值操作符的空實現,否則巨集的使用者將必須手動實現這兩個不會被也不該被使用的函式,而建構函式與解構函式的定義許可權,依舊保留給巨集的使用者,如果使用者不希望手動定義這兩個函式,也可以在cpp檔案中用配套的巨集SINGLETON_C_D來幫助自己節省一些力氣。

使用單例模式巨集時則類似這樣:

#include "SingletonDefine.h"

class Example
{
	SINGLETON(Example);
};
#define SGL_EXAMPLE Example::GetInstance()
由於不會產生模板風格的程式碼,因此省去了一個typedef。類使用方法與普通手寫的單例模式類(如1中所述那樣的類)沒有任何區別。

4.三種實現方式的分析

三種實現方式中類實現的可讀性最好--當然對於這麼幾行程式碼,其實可讀性對於任何人應該都不成問題。除此之外類實現的方式依賴性也最低,這裡指的依賴性不是類之間的依賴性而是檔案依賴性,即當代碼檔案被遷移到其他專案中去時,不需要額外附帶一個單例模式.h檔案(無論是模板的.h還是巨集的.h),缺陷顯而易見,當單例類比較多時,需要重複寫很多思路相同的程式碼。當寫一些可能被遷移的基礎類時--如底層資料管理類,可以考慮選用這種方式。

下面比較模板實現方式和巨集實現方式。兩種實現都會導致程式碼膨脹,但機制卻並不相同,巨集定義的替換屬於預處理,實際上是發生在編譯前,而模板實現則在編譯期生成實際程式碼,當然對於類的使用者來說並不會感知這種區別,尤其是有對應的typedef和define的幫助下。

但是,兩種實現方式依然有其他一些區別,這些區別足以影響應該選擇哪一種方式。

在模板實現一段中提到實際上作為“實現素材”的類Example並非單例,實際的單例是Singleton<Example>,也就是說使用模板實現將不可避免的在專案中產生兩個功能幾乎一樣的類,一個是作為實際使用的單例,而另一個則是它的“影子”,一個除了做單例模板的素材之外,再不會被使用的類。這將產生一個隱患:難以避免類的使用者在無意中使用那個影子類。而影子類一旦被使用,則單例則被打破,會出現什麼結果則難以預料。另一個更麻煩的問題是,如果Example中用到了列舉,則所有Example中的列舉都是屬於Example類的而非Singleton<Example>,即使編譯時生成了單例模式的程式碼,使用時卻無法徹底擺脫原先的影子類。

巨集實現沒有模板實現的上述缺點,它不會產生“影子類”,類中如果有列舉甚至巢狀類也不會影響使用。當然,大多數時候在C++中是不推薦使用巨集的,關於使用巨集的隱患和缺點,相關文章很多,此處不再展開。這裡要說的是,由於單例模式的實現巨集使用範圍非常狹窄,而且引數單一,因此,很多擔憂的問題實際上並不容易發生。如果暫時擱置巨集本身的問題,暫且只比較上面兩種實現的話,巨集實現明顯的弱點是:巨集定義比較醜,至少比模板定義醜,如果進行巢狀,醜陋指數加倍。

5.總結

優先使用巨集實現。

可能頻繁在多個專案中被遷移的程式碼,可以考慮使用最簡單的類實現方式。

類中沒有列舉,或者需要模板巢狀才能實現功能時,且能保證類使用者不會無意中使用“影子類”的情況下,使用模板實現。

6.補充

最近發現原來寫的巨集在vs下正常,而在eclipse下編譯時會有警告,原因是拷貝建構函式未將所有成員(除了容器)進行初始化。在C++11中可利用委託建構函式消除此警告。方法是將_CLASS_(_CLASS_ const&) {}改為_CLASS_(_CLASS_ const&) : _CLASS_(){}即可。但是在C++03下除了自己寫定義手動新增初始化列表之外我還未找到更好的辦法。

另外賦值操作符雖然不會被呼叫,但定義中未寫返回值,這也是不嚴謹的寫法,儘管在一些編譯器環境下可以通過編譯,但在其他編譯器上可能引起編譯錯誤或者警告,因此也加入了返回值。

更改之後的完整程式碼如下(編譯環境需支援C++11或以後的更新的版本規範):

#ifndef SINGLETON_DIFINE_H_
#define SINGLETON_DIFINE_H_

//單件類巨集
#define SINGLETON(_CLASS_)								\
public:													\
	inline static _CLASS_& GetInstance()				\
	{													\
	static _CLASS_ s_instance;							\
	return s_instance;									\
	}													\
private:												\
	_CLASS_();											\
	_CLASS_(_CLASS_ const&) : _CLASS_(){}				\
	_CLASS_& operator= (_CLASS_ const&){return *this;}	\
	~_CLASS_();											

//單件類的構造與解構函式(配合單件類巨集使用)
#define SINGLETON_C_D(_CLASS_)		\
	_CLASS_::_CLASS_(){}			\
	_CLASS_::~_CLASS_(){}			\

#endif //SINGLETON_DIFINE_H_

==========

====更新記錄====

2015-02-22:修正了部分程式碼排版問題和一些文字描述問題。

2015-04-27:修正了拷貝建構函式引起警告的問題,修正了賦值操作符定義的語法錯誤。