一、在GOF著作中对Singleton模式的实现方式如下:
/*解一*/
// Header file Singleton.h
class Singleton { public: static Singleton *Instance(){ //1 if( !m_pInstatnce) //2
m_pInstance = new Singleton;//3 return m_pInstance; //4 }
void DoSomething();private: static Singleton *m_pInstatnce=NULL; //5 private: Singleton(); //6 Singleton(const Singleton&); //7 Singleton& operator=(const Singleton&); //8 ~Singleton(); //9 }
// Implementation file Singleton.cpp
Singleton* Singleton::m_pInstance = 0;
客户代码现在可以这样使用Singleton:
1 Singleton &s = Singleton::Instance();
2 s.DoSomething();
在上面的解决方案中,我们只在需要调用时,才产生一个Singleton的对象。这样带来的好处是,
如果该对象产生带来的结果很昂贵,但不经常用到时,是一种非常好的策略。
但如果该Instance被频繁调用,就有人觉得Instance()中的判断降低了效率。
二、Meyers Singleton
我们如何解决这个问题呢,实际上很简单。一种非常优雅的做法由Scott Meyers最先提出,故也称为Meyers Singleton。它依赖编译器的神奇技巧,即函数内的static对象只在该函数第一次执行时才初始化(请注意不是static常量)。
/*解二*/
// Header file Singleton.h class Singleton { public: static Singleton *Instance(){ //1 static Singleton sInstance; //2 return &sInstance; //3 } private: Singleton(); //4 Singleton(const Singleton&); //5 Singleton& operator=(const Singleton&); //6 ~Singleton(); //7 }
// Implementation file Singleton.cpp
Singleton* Singleton::m_pInstance = 0;
解二在Instance中定义了一个Static的Singleton对象,来解决Instance中初始化的问题,也很顺利的解决了定义Static成员对象带来的问题。
请注意,解二在VC6中不能编译通过,将有以下的错误:
error C2248: 'Singleton::~Singleton' : cannot access private member declared in class 'Singleton' e:/work/q/a.h(81) : see declaration of 'Singleton::~Singleton'
产生该问题的错误原因是什么呢(请仔细思考^_^)
原因在于在产生static Singleton对象后,编译器会自动产生一个销毁函数__DestroySingleton(),然后调用atexit()注册,在程序退出时执行 __DestroySingleton。但由于Singleton的析构函数是private,所以会产生访问错误。(应该在以后的编译器中修改了该 BUG)
三、应用于多线程
解一和解二都不能用于多线程,要想用于多线程,还得引入锁机制。
将解一的Instance()改为如下:Singleton& Singleton::Instance(){ Lock(m_mutex); //1 If( !m_pInstance ){ //2 m_pInstance = new Singleton; //3 } UnLock(m_mutex); //4 return *m_pInstance; //5 }
此种方法将解决解一运行在多线程环境下内存泄漏的问题,但带来的结果是,当m_mutex被锁定时,其它试图锁定m_mutex的线程都将必须等等。并且每次执行锁操作其付出的代价极大,亦即是这种方案的解决办法并不吸引人。
那么我们将上面的代码改为如下方式:
Singleton& Singleton::Instance(){ If( !m_pInstance ){ //1 Lock(m_mutex); //2 m_pInstance = new Singleton; //3 UnLock(m_mutex); //4 } return *m_pInstance; //5 }
这样修改的结果没有问题了么?NO!!!!该方案带来的结果同解一,原因也一样,都将造成内存泄漏。此时“双检测锁定”模式就粉墨登场了。
由Doug Schmidt和Tim Harrison提出了“双检测锁定”(Double-Checked Locking)模式来解决multithread singletons问题。
Singleton& Singleton::Instance(){ If( !m_pInstance ){ //1 Lock(m_mutex); //含义为获取互斥量 //2 If(!m_pInstance) //3 m_pInstance = new Singleton; //4 UnLock(m_mutex); //5 } return *m_pInstance; //6 }
上面的方案就完美了么。回答还是NO!!!(各位看官是否已经郁闷了啊,这不是玩我啊?请耐心点,听我细细到来^_^)
如果在RISC机器上编译器有可能将上面的代码优化,在锁定m_mutex前执行第3句。这是完全有可能的,因为第一句和第3句一样,根据代码优化原则是可以这样处理的。这样一来,我们引以为自豪的“双检测锁定”居然没有起作用( L)
怎么办?解决呗。怎么解决?简单,我们在m_pInstance前面加一个修饰符就可以了。什么修饰符呢?……
volatile(简单吧)
那么我们完整的解法如下:
/*解三*/
// Header file Singleton.h class Singleton { public: static Singleton &Instance(){ //1 if( !m_pInstatnce){ //2 Lock(m_mutex) //3 If( !m_pInstance ) //4 m_pInstance = new Singleton;//5 UnLock(m_mutex); //6 } return *m_pInstance; //7 } private: static volatitle Singleton *m_pInstatnce; //8
static Mutex m_mutex;private: Singleton(); //9 Singleton(const Singleton&); //10 Singleton& operator=(const Singleton&); //11 ~Singleton(); //12 }
// Implementation file Singleton.cpp
Mutex Singleton::_mutex;Singleton *Singleton:m_pInstatnce = NULL; //13
四、Singleton销毁
在这里,我们就到了Singleton最简单也最复杂的地方了。
为什么说它简单?我们根本可以不理睬创建的对象m_pInstance的销毁啊。因为虽然我们一直没有将Singleton对象删除,但不会造成内存泄漏。为什么这样说呢?因为只有当你分配了积累性数据并丢失了对他的所有reference时,内存泄漏才发生。而对Singleton并不属于上面的情况,没有累积性的东东,而且直到结束我们还有它的引用。在现代操作系统中,当一个进程结束后,将自动将该进程所有内存空间完全释放。(可以参考《effective C++》条款10,里面讲述了内存泄漏)。
但有时泄漏还是存在的,那是什么呢?就是资源泄漏(resource leak)。比如说如果该Singleton对象管理的是网络连接,OS互斥量,进程通信的handles等等。这时我们就必须考虑到Singleton的销毁了。
解决方法是引入smart pointer
唯一修正 resource leak的方法就是在程序结束的时候delete _instance。当然了,用smart pointer再好不过,在这里用auto_ptr就可以满足需要了(如果你还不知道smart_ptr是什么,花点时间熟悉C++标准库吧),修改后的代码如下:
//解四
1 // Header file Singleton.h
2 class Singleton {
3 public :
4 static Singleton& Instance() { // Unique point of access
5 if (0 == m_pInstance)
6 m_pInstance=new Singleton();
7 return * m_pInstance;
8 }
9 void DoSomething(){}
10 private :
11 Singleton(){} // Prevent clients from creating a new Singleton
12 ~Singleton(){} // Prevent clients from deleting a Singleton
13 Singleton(const Singleton&); // Prevent clients from copying a Singleton
14 Singleton& operator=(const Singleton& );
15 private :
16 friend auto_ptr<Singleton> ;
17 static auto_ptr<Singleton> m_pInstance; // The one and only instance
18 };
19
20 // Implementation file Singleton.cpp
21 auto_ptr<Singleton> Singleton::m_pInstance;
五、不死鸟模式(Phoenix Singleton)
我们以KDL(keyboard,display,log)模型为例,其中K,D,L均使用Singleton模式。只要keyboard或者 display出现异常,我们就必须调用log将其写入日志中,否则log对象不应该创建。对后面一条,我们的Singleton创建时就可以满足。
在前面我们已经说到,在产生一个对象时(非用new产生的对象),由编译器自动调用了atexit(__DestroyObject)函数来实现该对象的析构操作。而C++对象析构是LIFO进行的,即先产生的对象后摧毁。
如果在一般情况下调用了log对象,然后开始销毁对象。按照“后创建的先销毁”原则:log对象将被销毁,然后display对象开始销毁。此时 display在销毁发现出现异常,于是调用log对象进行记录。但事实上,log对象已经被销毁,那么调用log对象将产生不可预期的后果,此问题我们称为Dead Reference。所以前面的解决方案不能解决目前我们遇到的问题。
Andrei Alexandrescu提出了解决方案,称为Phoenix Singleton。
修改后的代码如下: //解五
// Header file Singleton.h
class Singleton {
public :
static Singleton& Instance() {
if (0 == m_pInstance) {
Lock(m_mutex);
if(0==m_pInstance){
m_pInstance = new Singleton();
atexit(Destroy); // Register Destroy function
}
Unlock(m_mutex);
}
return * m_pInstance;
}
void DoSomething(){}
private :
static void Destroy() { // Destroy the only instance
if ( m_pInstance != 0 ) {
delete m_pInstance;
m_pInstance = 0 ;
}
}
Singleton(){} // Prevent clients from creating a new Singleton
~Singleton(){} // Prevent clients from deleting a Singleton
Singleton(const Singleton&); // Prevent clients from copying a Singleton
Singleton& operator=(const Singleton& );
private :
static Singleton *m_pInstance; // The one and only instance
};
// Implementation file Singleton.cpp
Singleton* Singleton::_instance = 0;
Mutex Singleton::_mutex;
对于不死鸟模式还有一种编程方式,目前理解还不太清楚,先记下来再说:
/*解六*/
class Singleton
{ public: static Singleton &Instance(){
if( !m_pInstatnce){ Lock(m_mutex);
If( !m_pInstance ){ if(m_destroyed) OnDeadReference(); else Create(); } UnLock(m_mutex); } return *m_pInstance; } private: static volatitle Singleton *m_pInstatnce; static bool m_destroyed; private: Singleton(); Singleton(const Singleton&); Singleton& operator=(const Singleton&); ~Singleton(){ m_pInstance = 0; m_destroyed = true; } static void Create(){ static Singleton sInstance; m_pInstanace = &sInstance; } static void OnDeadReference(){ Create(); new (m_pInstance) Singleton; atexit(KillPhoenixSingleton); m_destroyed = false; } void KillPhoenixSingleton(){ m_pInstance->~Singleton(); } } Singleton *Singleton:m_pInstatnce = NULL; bool m_destroyed =false;
请注意此处OnDeadReference()中所使用的new操作符的用法:是所谓的placement new操作,它并不分配内存,而是在某个地址上构造一个新对象。