Singleton模式

一、在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操作,它并不分配内存,而是在某个地址上构造一个新对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值