rambo

单例模式

Singleton 是对全局变量的取代策略
作用:保证一个类只能有一个实例,并提供一个全局唯一的访问点。可以认为单例模式是一种更加优雅的全局变量。不过相对于全局变量它提供了如下优点:

  • 确保一个类只创建一个实例;
  • 为对象分配和销毁提供控制;
  • 支持线程安全地访问对象的全局状态
  • 避免污染全局命名空间

在C++中实现单例模式:

单例模式要求创建一个类,它包含一个静态方法,每次调用该方法时返回该类的同一个实例。

  • 如果不想让客户创建新的实例,可以声明私有默认构造函数,从而阻止编译器将其自动声明为共有。
  • 如果想让单例不可复制以确保不能创建第二个实例,可以声明私有复制构造函数和私有赋值操作符。
  • 如果想禁止客户删除实例可以声明私有析构函数。
  • GetInstance()方法既可以返回单例类的指针也可以返回引用。当返回指针时客户可以删除该对象。所以最好返回引用。

注意:

1、如果把构造函数声明为私有,那么这样创建实例是会报错的Singleton * a=new Singleton; 也就是阻止了创建新的实例。同理如果把构造函数声明为public,而析构函数声明为私有delete a; 这样删除单例实例也是会报错的。

2、声明私有构造函数和析构函数意味着客户不能创建单例的子类。如果想要允许这样做,可以将这两个函数声明为protected。

3、将构造函数、析构函数、复制构造函数以及赋值操作符声明为私有的(或受保护的),可以实现单例模式。

 如何分配单例实例?

C++中初始化问题:不同编译单元中的非局部静态对象的初始化顺序是未定义的。

参见 EffectiveCpp 条款47: 确保非局部静态对象在使用前被初始化

也就是说使用非局部静态变量初始化单例是很危险的。非局部对象是指声明在函数之外的对象。初始化单例的途径之一是在类的方法中创建静态变量。

这种方法的优点就是在GetInstance()方法第一次调用时才分配实例。如果单例从没有被请求过就不会分配对象。不利的方面是,该方式不是线程安全的。可以这样理解,在单例的静态初始化中存在竞态条件。使用单例模式创建对象的时候 ,如果碰巧有两个线程同时调用该方法,那么该实例就有可能被构造两次。或者在一个线程完全初始化实例之前另一个线程就调用了该实例。如果多个对象同时被创建,又同时被修改或调用就有可能导致了理论值和结果值的不一致,此时线程即是不安全的。

举个形象的例子:两个人同时调用一个方法(给我蛋糕),但这个方法返回一个蛋糕的单例对象,两个人同时获得了同一个蛋糕,并坐下,举起刀叉,结果第一个人先吞了蛋糕,就造成了第二个人明明得到了蛋糕,却没能吃到这个结果。

补充知识:

竞态条件(race condition),从多进程间通信的角度来讲,是指两个或多个进程对共享的数据进行读或写的操作时,最终的结果取决于这些进程的执行顺序。竞态条件(race condition)是指设备或系统出现不恰当的执行时序,而得到不正确的结果。

线程安全:对于多线程来讲,如果所使用的公用变量在多线程下没有被保护机制时,变量结果会和理论值不一致,这样就叫作线程不安全,相反公用变量在保护机制下工作,就不会出现未知变化,那这样线程就是安全的。

解决方法:在表现出竟态条件的代码前后添加互斥锁(mutex)实现线程安全。

该方法的潜在问题就是开销较大,方法每次调用时都需要请求加锁。

要优化此类激进的加锁行为通常采用双重检查锁定方式(Double Check Locking Pattern,DCLP)

只有当instance为NULL即没有创建时,需要加锁操作当instance已经创建出来之后,则无需加锁。但是DCLP并不能保证在所有编译器和所有处理器内存模型下都能正常工作。例如共享内存的对称多处理器通常突发式提交内存写操做,这会造成不同线程的写操作重新排序。这种问题的常见解决方案是使用volatile关键字,因为它能将读写操作同步到易变数据中。

换一种思路考虑避免使用之前提到的惰性实例化模型,而在启动时就初始化单例。例如在main()调用之前初始化或者使用互斥锁保护的API初始化调用。

1)静态初始化。将创建单例实例作为静态初始化器的一部分,从而避免了使用互斥锁的需求。在singleton.cpp文件中添加以下静态初始化调用 .

 

 

 

《设计模式》一书中给出了一种很不错的实现,定义一个单例类,使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。

仅有一个实例:通过类的静态成员变量来体现。
提供访问它的全局访问点:访问静态成员变量的静态成员函数来体现。

单例模式通过类本身来管理其唯一实例,这种特性提供了解决问题的方法。唯一的实例是类的一个普通对象,但设计这个类时,让它只能创建一个实例并提供对此实例的全局访问。唯一实例类Singleton在静态成员函数中隐藏创建实例的操作。习惯上把这个成员函数叫做Instance(),它的返回值是唯一实例的指针。

单例类Singleton有以下特征:

它有一个指向唯一实例的静态指针,并且是私有的;

它有一个公有的函数,可以获取这个唯一的实例,并且在需要的时候创建该实例;

它的构造函数是私有的,这样就不能从别处创建该类的实例。

UML图:

在Singleton模式的结构图中可以看到,我们通过维护一个static的成员变量_instance来记录这个唯一的对象实例。通过提供一个staitc的接口Instance来获得这个唯一的实例。

代码如下:

Singleton不可以被实例化,因此我们将其构造函数声明为protected或者直接声明为private。

 

实用类提供系统公用的静态静态方法,并且也经常采用私有化的构造函数,与单件模式不同,它没有实例,其中的方法全部是静态方法。

单件提供了全局唯一的访问入口,易于控制可能发生的冲突。单件是对类静态函数的一种改进,首先改变了全局变量对系统的污染,其次它可以有子类,可以定义虚函数,可以有子类,而类的静态方法是不能定义为虚函数的,因此不具有多态性。

单件模式可以扩展为多件,即允许有受控的多个实例存在。

另外,单件模式维护了自身的实例化,在使用时是安全的,类静态函数无法自行维护

 

参考:

http://baike.baidu.com/link?url=SIRLL6poywqmykxZV548ksqiRnxS4OW0usjYNKZob06Ey0mCpj8ms3rhu401ZHeMDSQglw1AQOKnYzlWp25KMK

//http://blog.csdn.net/v1v1wang/article/details/5511756

http://www.cnblogs.com/yinhaiming/articles/1540098.html

http://jingyan.baidu.com/article/7e440953cffd192fc1e2ef6e.html

http://www.cnblogs.com/GODYCA/archive/2012/11/22/2783408.html