Chinaunix首页 | 论坛 | 博客
  • 博客访问: 513933
  • 博文数量: 238
  • 博客积分: 10208
  • 博客等级: 上将
  • 技术积分: 2820
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-12 09:27
文章分类

全部博文(238)

文章存档

2010年(50)

2009年(66)

2008年(122)

我的朋友

分类: C/C++

2009-11-25 18:50:58

一、在GOF著作中对Singleton模式的实现方式如下

  1. /*解一*/   
  2. // Header file Singleton.h
  3. class Singleton   
  4. {   
  5. public:   
  6. static Singleton *Instance(){       //1   
  7. if( !m_pInstatnce) //2
  8. m_pInstance = new Singleton;//3   
  9. return m_pInstance; //4   
  10. }   
  11. void DoSomething();
  12. private:   
  13. static Singleton *m_pInstatnce=NULL; //5   
  14. private:   
  15. Singleton(); //6   
  16. Singleton(const Singleton&); //7   
  17. Singleton& operator=(const Singleton&); //8   
  18. ~Singleton();  //9   
  19. }     

  20. // Implementation file Singleton.cpp
  21. 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常量)。

  1. /*解二*/  
  2. // Header file Singleton.h 
  3. class Singleton   
  4. {   
  5. public:   
  6.     static Singleton *Instance(){  //1   
  7.         static Singleton sInstance; //2   
  8.         return &sInstance; //3   
  9.     }   
  10. private:   
  11.     Singleton(); //4   
  12.     Singleton(const Singleton&);  //5   
  13.     Singleton& operator=(const Singleton&);  //6   
  14.     ~Singleton(); //7   
  15. }   
  16.  

  17. // Implementation file Singleton.cpp
  18. Singleton* Singleton::m_pInstance = 0;

解二在Instance中定义了一个Static的Singleton对象,来解决Instance中初始化的问题,也很顺利的解决了定义Static成员对象带来的问题。

请注意,解二在VC6中不能编译通过,将有以下的错误:

  1. 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()改为如下:
  1. Singleton& Singleton::Instance(){   
  2.     Lock(m_mutex);       //1   
  3.     If( !m_pInstance ){   //2   
  4.         m_pInstance = new Singleton; //3   
  5.     }   
  6.     UnLock(m_mutex);     //4   
  7.     return *m_pInstance; //5   
  8. }   
  9.  

此种方法将解决解一运行在多线程环境下内存泄漏的问题,但带来的结果是,当m_mutex被锁定时,其它试图锁定m_mutex的线程都将必须等等。并且每次执行锁操作其付出的代价极大,亦即是这种方案的解决办法并不吸引人。

那么我们将上面的代码改为如下方式:

  1. Singleton& Singleton::Instance(){   
  2.     If( !m_pInstance ){  //1   
  3.         Lock(m_mutex);  //2   
  4.         m_pInstance = new Singleton; //3   
  5.         UnLock(m_mutex); //4   
  6.     }   
  7.     return *m_pInstance;  //5   
  8. }   
  9.  

这样修改的结果没有问题了么?NO!!!!该方案带来的结果同解一,原因也一样,都将造成内存泄漏。此时“双检测锁定”模式就粉墨登场了。

由Doug Schmidt和Tim Harrison提出了“双检测锁定”(Double-Checked Locking)模式来解决multithread singletons问题。

  1. Singleton& Singleton::Instance(){   
  2.     If( !m_pInstance ){   //1   
  3.         Lock(m_mutex); //含义为获取互斥量 //2   
  4.         If(!m_pInstance) //3   
  5.             m_pInstance = new Singleton; //4   
  6.         UnLock(m_mutex); //5   
  7.     }   
  8.     return *m_pInstance; //6   

上面的方案就完美了么。回答还是NO!!!(各位看官是否已经郁闷了啊,这不是玩我啊?请耐心点,听我细细到来^_^)

如果在RISC机器上编译器有可能将上面的代码优化,在锁定m_mutex前执行第3句。这是完全有可能的,因为第一句和第3句一样,根据代码优化原则是可以这样处理的。这样一来,我们引以为自豪的“双检测锁定”居然没有起作用( L)

怎么办?解决呗。怎么解决?简单,我们在m_pInstance前面加一个修饰符就可以了。什么修饰符呢?……

volatile(简单吧)

那么我们完整的解法如下:

  1. /*解三*/  
  2. // Header file Singleton.h 
  3. class Singleton   
  4. {   
  5. public:   
  6.     static Singleton &Instance(){  //1   
  7.         if( !m_pInstatnce){ //2   
  8.             Lock(m_mutex) //3   
  9.             If( !m_pInstance ) //4   
  10.                 m_pInstance = new Singleton;//5   
  11.             UnLock(m_mutex); //6   
  12.         }   
  13.         return *m_pInstance; //7   
  14.      }   
  15. private:   
  16.      static volatitle Singleton *m_pInstatnce;   //8  
  17.      static Mutex m_mutex;
  18. private:   
  19.      Singleton();    //9   
  20.      Singleton(const Singleton&);  //10   
  21.      Singleton& operator=(const Singleton&); //11   
  22.      ~Singleton();   //12   
  23. }  
  24. // Implementation file Singleton.cpp 
  25. Mutex Singleton::_mutex;
  26. 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;


对于不死鸟模式还有一种编程方式,目前理解还不太清楚,先记下来再说:

  1. /*解六*/
  2. class Singleton   
  3. {   
  4. public:   
  5.     static Singleton &Instance(){                              
  6.         if( !m_pInstatnce){   
  7.             Lock(m_mutex);  
  8.             If( !m_pInstance ){   
  9.                 if(m_destroyed)   
  10.                     OnDeadReference();   
  11.                 else   
  12.                     Create();   
  13.             }   
  14.             UnLock(m_mutex);   
  15.         }   
  16.         return *m_pInstance;   
  17.     }   
  18. private:   
  19.     static volatitle Singleton *m_pInstatnce;   
  20.     static bool m_destroyed;   
  21. private:   
  22.     Singleton();                                                           
  23.     Singleton(const Singleton&);                               
  24.     Singleton& operator=(const Singleton&);       
  25.     ~Singleton(){   
  26.         m_pInstance = 0;   
  27.         m_destroyed = true;   
  28.     }   
  29.     static void Create(){   
  30.         static Singleton sInstance;   
  31.         m_pInstanace = &sInstance;   
  32.     }   
  33.     static void OnDeadReference(){   
  34.         Create();   
  35.         new (m_pInstance) Singleton;   
  36.         atexit(KillPhoenixSingleton);   
  37.         m_destroyed = false;   
  38.     }   
  39.     void KillPhoenixSingleton(){   
  40.         m_pInstance->~Singleton();   
  41.     }   
  42. }   
  43. Singleton *Singleton:m_pInstatnce = NULL;   
  44. bool m_destroyed =false;    
  45.  

请注意此处OnDeadReference()中所使用的new操作符的用法:是所谓的placement new操作,它并不分配内存,而是在某个地址上构造一个新对象。

阅读(532) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~