您的位置:首页 > 编程语言 > C语言/C++

Singleton设计模式的C++实现详解

2013-10-25 16:32 337 查看
Singleton

The Singleton Pattern: ensures a class has only one instance, and provides a global point of access to it.

只有一个实例的类,如下是若干考虑:

 首先,要产生类实例,需要调用构造函数。为了防止用户申明或者new一个类的实例,我们可以把这个类的构造函数设置为protected或者private,那么用户申明或者new就不可能编译通过,当然,delete也是不允许的,也申明为protected或者private

 可是,我们在哪里创建类实例呢?虽然外部不能创建类的对象,但是类的内部,是可以调用构造函数,从而可以创建类实例的。我们给类的外部提供一个方法GetInstance()。它在没有类实例时创建类实例并返回实例(指针或引用);在有实例时,直接返回已有实例。这样我们就需要保存实例的指针以备GetInstance()使用,该指针也应该定义在类上,否则在没有类实例之前,怎么给这个成员变量赋值呢?如此,第一个版本得到:

  class   Singleton   

  {   

  public:   

          static   Singleton&   Instance()   

          {   

                  if   (!si_instance)   

                  {   

                          si_instance   =   new   Singleton;   

                  }   

                  return   *si_instance;   

          }   

  private:   

          //禁止外部构造函数调用 

          Singleton(){}

          //对象静态指针,用于保存创建的对象

          static   Singleton*   si_instance; //声明静态数据成员

  };

  Singleton* Singleton::si_instance=0; //定义并初始化全局存贮,局部作用的静态数据成员

 Static用在类外:

 static和extern都是變量的存贮类型,申明為static的變量,不能被其他文件(指目标文件)引用,extern可以被本文件和其他文件引用(指目标文件)。不能同時使用它們申明同一個變量。它們的共同點是存放位置都在程序的static   memory中(全局存贮)。 

 其中static可以用于局部變量的申明,此時每次Call   Function時,其中的Static局部變量的值不執行初始化,即保留上一次引用結束後的值。

 extern是default的申明。   

 不提倡使用static局部變量。   

 具體的使用還與編譯器有關,以上的說明建立在ANSI   C所定義的C   Complier基礎上。 

 CPP文件产生目标文件,显然,CPP文件include的头文件中的static或extern变量在CPP中仍然有效(非只在头文件中有效)。链接时,static变量只在本obj内可引用,而extern则在所有obj内可引用(这样就可能会重名,出现重定义错误,所以才引入static关键字)。对于静态全局函数,也是用于限制这个全局函数在本obj文件中。

 Static用在类定义中:

 类是类型的定义。类中的所有成员的定义均是一个申明,说明这个类的内部构成。只有产生类的实例的代码,才需要为类的成员分配存贮空间,或在栈上,或在堆中。

 为处理定义在类上的变量,我们用static修饰该成员。这时static的语义是该成员是一个extern类型的全局变量。由于类定义是申明,比如在类fun中申明static int i,而i并没有被定义(产生实例),我们需要在类的外部定义并初始化fun::i,这就是为什么类中的静态变量必须初始化,且必须在类外初始化。

 然而,这又带来了额外的问题,那就是如果在多个CPP都包含了类fun定义文件,那么对fun::i的定义并初始化语句就会在多个CPP文件中出现,链接就无法通过!

 这就是为什么要区分头文件和CPP文件的原因。我们把“申明”写在头文件中,把“定义”(也就是会产生实例的代码)写在CPP文件中,因为我们从不include CPP文件,所以,定义fun::i只有一次!!!这个原则同样适用于类的方法。类的方法f()如果在类的外部实现,也应该写在CPP中,否则当头文件被多个CPP文件引用时,就会出错。如果把成员函数体直接写在类中,那么不会有问题,尽管多个包含类头文件的CPP文件的obj都产生了fun::f(),但是,这个函数定义的作用域不会超出这个类的代码块,因此不会冲突。编译时实际上产生了这个函数的多个副本,每个副本只在局部作用。链接时用到的副本会写入exe文件,甚至被处理为内联函数。相对的,如果把fun::f()写在类的实现CPP文件中,那么就只在编译时产生一个fun::f()代码,看起来编译会较少花费时间。

 对于类的成员函数,用static修饰时,表示它是一个定义在类上的方法,函数并没有存贮类别一说,这个寒暑不需要传递this指针就可以调用,即通过类名就可以引用,当然,如果创建了类对象,通过对象也可以引用。普通的成员函数则需要通过this指针来使用(实际上是传递了this指针给那个函数)

 小结:在定义性代码(类外的代码)中,static保证修饰对象生存期为程序运行全过程,在代码块中定义,作用域在代码块中,在代码块外(全局定义),作用域是obj文件

在申明性代码中(类中),static保证修饰对象的生存期是程序执行全过程,作用域是全局,因为是申明,不是定义,所以对于成员变量需要在类外部定义全局的成员,对于成员函数在外部和内部定义都可以,因为static仅说明不传递this指针给该函数。

 这个版本有一个问题,就是对象创建了,可是在哪里删除这个对象呢?我们是在类的内部new对象的,外部没有new,那么外部也不应该去delete。Si_instance是一个静态变量,用来保存指针,它实际上是一个全局变量,也就是说,它在进程加载时存在,在进程退出时才销毁(main()程序结束后)。那么能不能在si_instance销毁时删除对象呢?可以,如果我们使用标准库的的auto_ptr,但是,为了使得auto_ptr能够delete创建的对象,需要把它设置为Singleton的友元类,得到如下可测试的代码:

//Singleton.h

#include <memory>  

#include <iostream>

using namespace std;

class   Singleton   

  {   

   friend class auto_ptr<Singleton>;

  public:   

          static   Singleton*   Instance()   

          {   

                  if   (!si_instance.get())   

                  {   

                          si_instance.reset(new   Singleton);   

                  }   

                  return   si_instance.get();   

          } 

   

  private:   

          ~Singleton(){cout<<"delete Singleton";} //禁止外部delete

          //禁止外部构造函数调用 

          Singleton(){}

          //对象静态指针,用于保存创建的对象

          static   auto_ptr<Singleton>   si_instance;

  };

//Singleton.cpp

#include “Singleton.h”

auto_ptr<Singleton> Singleton::si_instance(NULL); //静态变量需要在类的外部初始化

//test.cpp

#include “Singleton.h”

void main()

{

 Singleton* ps=Singleton::Instance();

}

 在单线程的情况下,上面的代码已经可以了,但是如果有多个线程,那么,在一开始,有两个线程调用Instance,发现指针为空,于是两个线程都创建对象,就会出错,也就是说上面的代码线程不安全。那么线程怎么同步呢?使用CriticalSection是一个好办法。可以使用windows API的CriticalSection也可以使用MFC的CCriticalSection。后者使用要简单一些,不需要初始化,可以有lock,unlock方法方便使用,可是要用到MFC,根据奥卡姆剃刀原则,能不引入额外的概念就不引入额外的概念。因此使用前者。但是问题是:

 在哪里定义CriticalSection?它最好具有局部作用域,同时又在程序执行期间生存,考虑到它和Singleton紧密相关,那么把它声明为Singleton的一个static成员是最合适的了,在类外定义它(Singleton.cpp中)

 此外,CriticalSection需要调用initiallize()来初始化,在哪里执行这个语句呢?在main()函数中显然不可以,客户不应该关心Singleton的线程安全实现;在Singleton的构造函数中?也不行,构造函数在创建Singleton对象创建之后调用,而在此之前,CriticalSection就必须工作了!可行的办法有一个,就是让Singleton有一个CresGuard类型的静态类成员,那么这个静态类成员需要在类外初始化吧?那么它的构造函数会被调用吧?好,在其构造函数中initiallize()
CriticalSection。这样想似乎没有问题,在Singleton里定义静态的CriticalSection,和静态的CresGuard,然后,在CresGuard的构造函数里初始化CriticalSection。可是,万一CresGuard初始化时,构造函数调用,而此时CriticalSection还没有创建呢?这会导致initialize()的调用失败。它们都是局部作用,全程生存,程序不能依赖于他们的创建顺序。那么,就把CriticalSection放到CresGuard里,作为一个成员吧。这下问题又来了,CresGuard的静态成员CriticalSection必须在CresGuard类外初始化,在构造函数中是不行的!那么就定义其为非静态的吧,因为CresGuard是静态的,CriticalSection就不需要定义为静态了。得到如下代码:

//CresGuard.h

#include   <windows.h> 

class CresGuard

{

private:

 CRITICAL_SECTION cs; //临界区

public:

 CresGuard(){InitializeCriticalSection(&cs);}

 ~CresGuard(){DeleteCriticalSection(&cs);}

 void lock(){EnterCriticalSection(&cs);}

 void unlock(){LeaveCriticalSection(&cs);}

};

//Singleton.h

#include "CresGuard.h"

#include <memory>  

#include <iostream>

using namespace std;

class   Singleton   

  {   

   friend class auto_ptr<Singleton>;

  public:

   static CresGuard cresguard;

  public:   

          static   Singleton*   Instance()   

          {   

     cresguard.lock(); 

     if   (!si_instance.get())   

      {   

        si_instance.reset(new   Singleton);   

      }   

     cresguard.unlock();

                  return   si_instance.get();   

          } 

  private:   

      ~Singleton(){cout<<"delete Singleton";} //作为测试用

        //禁止外部构造函数调用 

          Singleton(){}

          //对象静态指针,用于保存创建的对象

          static   auto_ptr<Singleton>   si_instance;

  };

//test.cpp

#include "Singleton.h"

  void main()

{

 Singleton* ps=Singleton::Instance();

}

 现在代码线程安全了,可是,如果一个线程进入临界区代码并发生异常,那么我们不能保证unlock()会被执行,那么其他线程调用Instance时就会挂起等待。办法之一就是使用try..catch语句,保证unlock()得到执行。更好的办法是,所有栈上的变量系统保证离开该代码块时销毁,那么如果用一个自动变量,其创建时lock,销毁时unlock,就可以不同try…catch语句。我们把这个自动变量的类型处理为类类型,在其构造函数和析构函数中lock和unlock,如果我们把它看成一个锁,那么在临界区代码块开头就定义一个这个锁,在临界区代码块(用{}括起来)结束时锁自动销毁解锁。这个锁的类型定义,干脆就放在CresGuard里,使之成为一个整体。在取名上,我们处理为这个锁锁CresGuard,即类名是LockCresGuard。得到如下代码:

//CresGuard.h

#include   <windows.h> 

class CresGuard

{

private:

 CRITICAL_SECTION cs; //临界区

 void lock(){EnterCriticalSection(&cs);}

 void unlock(){LeaveCriticalSection(&cs);}

public:

 CresGuard(){InitializeCriticalSection(&cs);}

 ~CresGuard(){DeleteCriticalSection(&cs);}

  //嵌套类,构造析构调用CresGuard对象.lock/unlock

 class LockCresGuard

 {

 private:

  CresGuard& _cres;

 public:

  LockCresGuard(CresGuard& cres):_cres(cres){_cres.lock();}

  ~LockCresGuard(){_cres.unlock();}

 };

};

//Singleton.h

#include "CresGuard.h"

#include <memory>  

#include <iostream>

using namespace std;

class   Singleton   

  {   

   friend class auto_ptr<Singleton>;

  private:

     static CresGuard cresguard;

public:

  public:   

          static   Singleton*   Instance()   

          {   

     //下面声明锁变量,其初始化时调用cresguard.lock

     CresGuard::LockCresGuard lock(cresguard); 

     if   (!si_instance.get())   

      {   

        si_instance.reset(new   Singleton);   

      }   

                  return   si_instance.get();   

          } //这里lock析构,调用cresguard.unlock

  private:   

      ~Singleton(){cout<<"delete Singleton";} //作为测试用

        //禁止外部构造函数调用 

          Singleton(){}

          //对象静态指针,用于保存创建的对象

          static   auto_ptr<Singleton>   si_instance;

  };

//Singleton.cpp

#include "Singleton.h"

CresGuard Singleton::cresguard;

auto_ptr<Singleton> Singleton::si_instance(NULL); //静态变量需要在类的外部初始化

//test.cpp

#include "Singleton.h"

  void main()

{

 Singleton* ps=Singleton::Instance();

 //ps=new Singleton; //无法执行,构造私有

 //delete ps; //无法执行,析构私有

}

 基本上成型了,但是,每次调用Singleton的Instance时,都要同步线程,可以说是很不好的,因为会影响性能,该函数被多个线程反复多次地调用,甚至频繁地调用,我们可以使用double_check lock,也就是说:先看看si_instance.get()空不空,不空,直接返回,空再同步线程(加锁),再看看空不空,若空创建实例,若不空,返回。这样做的好处是,在Singleton创建第一个实例后,后面的调用都不同步线程,当然也不创建锁的自动变量,程序速度也提高了。Double check是为了提升性能,线程安不安全,要看临界区,不相干的。修改代码如下:

          static   Singleton*   Instance()   

          {   

     

     if   (!si_instance.get())   

      { //下面声明锁变量,其初始化时调用cresguard.lock  

       CresGuard::LockCresGuard lock(cresguard); 

       if   (!si_instance.get())   

       si_instance.reset(new   Singleton);   

      } //这里lock析构,调用cresguard.unlock  

                  return   si_instance.get();   

          }

 类写完了,如何通用?

 首先很容易将Singleton改写为返回一个T型实例的模板,只要修改若干位置即可。

 那我有一个返回T型实例的Singleton,我能不能构造一个单实例的类?当然可以,只要:

 定义一个新类,将其构造和析构函数设置为私有或保护

 将Singleton<该类>和auto_ptr<该类>设为该类的有元,前者要调用该类的构造函数,后者要调用该类的析构函数

//Singleton.h

#include "CresGuard.h"

#include <memory>  

#include <iostream>

using namespace std;

template <class T>

class   Singleton   

  {   

   friend class auto_ptr<T>;

  private:

     static CresGuard cresguard;

public:

  public:   

          static   T*   Instance()   

          {   

     

     if   (!si_instance.get())   

      { //下面声明锁变量,其初始化时调用cresguard.lock  

       CresGuard::LockCresGuard lock(cresguard); 

       if   (!si_instance.get())   

       si_instance.reset(new   T);   

      } //这里lock析构,调用cresguard.unlock  

                  return   si_instance.get();   

          } 

  private:   

      ~Singleton(){cout<<"delete Singleton";} //作为测试用

        //禁止外部构造函数调用 

          Singleton(){}

          //对象静态指针,用于保存创建的对象

          static   auto_ptr<T>   si_instance;

  };

template <class T>

CresGuard Singleton<T>::cresguard;

template <class T>

auto_ptr<T> Singleton<T>::si_instance(NULL); //静态变量需要在类的外部初始化

注意静态变量被挪到头文件中了,因为模板表示类型的类型,模板的实例化是类型,这是因为如果放在原CPP文件中,编译时不知道用什么类型带入T,因为这时是编译Singleton.h+Singleton.CPP,编译器不知道使用哪个具体的类带入T;而在编译myclass.cpp+Singleton.h,又没有singleton.cpp,结果就静态变量被当作了外部符号,链接时就会出错。目前VC都是采用包含编译的方式编译模板,也就是编译时,必须包含模板的定义,而不仅仅是声明

相关的其他代码:

//myclass.h

#include "Singleton.h"

class myclass

{

 friend  class Singleton<myclass>;

 friend  class auto_ptr<myclass>;

private:

 myclass(){cout<<"Construct myclass/n";}

 ~myclass(){cout<<"Destruct myclass/n";}

public:

 static myclass* Instance(){return Singleton<myclass>::Instance();}

};

//test.cpp

#include "myclass.h"

  void main()

{

 myclass* pmyclass=myclass::Instance();

 myclass* pmyclass1=myclass::Instance();

 myclass* pmyclass2=myclass::Instance();

}

 但是,写起来还是麻烦,用宏整理一下:

//Singleton.h

#include "CresGuard.h"

#include <memory>  

#include <iostream>

using namespace std;

template <class T>

class   Singleton   

  {   

   friend class auto_ptr<T>;

  private:

     static CresGuard cresguard;

public:

  public:   

          static   T*   Instance()   

          {   

     

     if   (!si_instance.get())   

      { //下面声明锁变量,其初始化时调用cresguard.lock  

       CresGuard::LockCresGuard lock(cresguard); 

       if   (!si_instance.get())   

       si_instance.reset(new   T);   

      } //这里lock析构,调用cresguard.unlock  

                  return   si_instance.get();   

          } 

  private:   

      ~Singleton(){cout<<"delete Singleton";} //作为测试用

        //禁止外部构造函数调用 

          Singleton(){}

          //对象静态指针,用于保存创建的对象

          static   auto_ptr<T>   si_instance;

  };

template <class T>

CresGuard Singleton<T>::cresguard;

template <class T>

auto_ptr<T> Singleton<T>::si_instance(NULL); //静态变量需要在类的外部初始化

//下面是宏定义,用/连接多行,表示一行

#define DECLARE_SINGLETON(type) /

 friend  class Singleton<type>;/

 friend  class auto_ptr<type>;/

private:/

 type(){cout<<"Construct type/n";}/

 ~type(){cout<<"Destruct type/n";}/

public:/

 static type* Instance(){return Singleton<type>::Instance();}

使用:

#include "Singleton.h"

class myclass

{

DECLARE_SINGLETON(myclass)

… …

};

 每一个Singleton也许有其自己的个性,在构造和析构的时候,可能会有额外的操作,我们将其延伸至以下两个纯虚函数中来,由每个Singleton类自己去完成,最终代码:

//CresGuard.h

#include   <windows.h> 

class CresGuard

{

private:

 CRITICAL_SECTION cs; //临界区

 void lock(){EnterCriticalSection(&cs);}

 void unlock(){LeaveCriticalSection(&cs);}

public:

 CresGuard(){InitializeCriticalSection(&cs);}

 ~CresGuard(){DeleteCriticalSection(&cs);}

  //嵌套类,构造析构调用CresGuard对象.lock/unlock

 class LockCresGuard

 {

 private:

  CresGuard& _cres;

 public:

  LockCresGuard(CresGuard& cres):_cres(cres){_cres.lock();}

  ~LockCresGuard(){_cres.unlock();}

 };

};

//Singleton.h

#include "CresGuard.h"

#include <memory>  

#include <iostream>

using namespace std;

template <class T>

class   Singleton   

  {   

   friend class auto_ptr<T>;

  private:

     static CresGuard cresguard;

public:

  public:   

          static   T*   Instance()   

          {   

     

     if   (!si_instance.get())   

      { //下面声明锁变量,其初始化时调用cresguard.lock  

       CresGuard::LockCresGuard lock(cresguard); 

       if   (!si_instance.get())   

       si_instance.reset(new   T);   

      } //这里lock析构,调用cresguard.unlock  

                  return   si_instance.get();   

          } 

  private:   

      ~Singleton(){cout<<"delete Singleton";} //作为测试用

        //禁止外部构造函数调用 

          Singleton(){}

          //对象静态指针,用于保存创建的对象

          static   auto_ptr<T>   si_instance;

  };

template <class T>

CresGuard Singleton<T>::cresguard;

template <class T>

auto_ptr<T> Singleton<T>::si_instance(NULL); //静态变量需要在类的外部初始化

#define DECLARE_SINGLETON(type) /

 friend  class Singleton<type>;/

 friend  class auto_ptr<type>;/

protected:   /

private:/

 type(){cout<<"Construct type/n";InitialInstance();}/

 ~type(){cout<<"Destruct type/n";DisposeInstance();}/

public:/

 static type* Instance(){return Singleton<type>::Instance();}

//myclass.h

#include "Singleton.h"

class myclass

{

//下面几行必须有

DECLARE_SINGLETON(myclass)

protected:   

          virtual   void   InitialInstance(){cout<<"InitialInstance";}   

          virtual   void   DisposeInstance(){cout<<"DisposeInstance";} 

//下面可以写自己想写的... ...

};

//test.cpp

#include "myclass.h"

  void main()

{

 //ps=new Singleton; //无法执行,构造私有

 //delete ps; //无法执行,析构私有

 myclass* pmyclass=myclass::Instance();

 myclass* pmyclass1=myclass::Instance();

 myclass* pmyclass2=myclass::Instance();

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: