记得以前大家讨论过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。又因为我有全局变量保证,就可以不考虑多线程问题了。
要么是这个问题,要么是那个问题,你总要解决一个问题。