記得以前大家討論過Singleton三種寫法的優劣,今天我又發現了一個新問題,在這里和大家分享一下。下面是三種Singleton的寫法:
1 // 1st
2 class Singleton{
3 static Singleton inst;
4 public:
5 Singleton& GetInst(){
6 return inst;
7 }
8 };
9 Singleton Singleton::inst;
10 // 2nd
11 class Singleton{
12 static Singleton* _inst;
13 public:
14 Singleton& GetInst(){
15 if(_inst == NULL)
16 _inst = new Singleton;
17 return *_inst;
18 }
19 };
20 // 3rd
21 class Singleton{
22 public:
23 Singleton& GetInst(){
24 static Singleton inst;
25 return inst;
26 }
27 }
我們已經知道方法1沒有多線程問題,但編譯時分配內存。方法2有多線程問題,在運行時分配內存。方法3也有多線程問題,而且在編譯時分配內存,但在運行時調用構造函數。(多線程問題的解決方法在此不再贅述。)
那么,我們可能就認為方法1雖然在編譯時分配內存,但我們不在乎這點內存,反正寫出來是要用的,這點內存少不了,既避免了多線程,又避免了分配失敗。就用它了!
不幸的是,方法1也有它自身的問題。今天我在構造一個對象工廠時,期望通過全局變量,向工廠注冊派生類的生成方法時,方法1暴露了它的問題。比如我們在Singleton中有一個方法RegisterMethod(CallBack*),那么我可能這樣實現。
1 //以下代碼是全局的,文件級作用域
2
3 namespace{
4 CBase* CreateDeriveObj()
5 {
6 return new(CDerive);
7 }
8 BOOL tmp = Singleton::GetInst().RegisterMethod(CreateDeriveObj);
9 }//end of namespace
結果發現在調用RegisterMethod()時,Singleton::inst 還沒有初始化(構造函數沒有被調用)。究其原因是編譯器雖然在編譯時對其分配內存,但是構造函數是在運行時,在Main()函數前調用。而對于全局變量,編譯器是不保證初始化順序的!而這個例子就是tmp在構造時, Singleton::inst 還沒有構造。
而這個問題的解決方法只有使用方法2或者方法3,考慮到我們不能在Main函數前使用new操作符,我用了方法3。又因為我有全局變量保證,就可以不考慮多線程問題了。
要么是這個問題,要么是那個問題,你總要解決一個問題。