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

Share and Enjoy:
  • Print this article!
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • LinkedIn
  • Live
  • MySpace
  • RSS
  • Slashdot
  • Technorati
  • TwitThis

Related posts:

  1. Codelite代码分析之 Singleton Pattern Template实现及应用
  2. CodeLite Plugin Internal(1) – IPlugin interface
  3. AVL树Source Code
  4. CodeLite Initializaion analysis - 初始化流程分析(1)

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

*
To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Click to hear an audio file of the anti-spam word

Contact us

Admin: Bryan Wu