单例模式:在一个应用程序中,一个类有且只有一个实例,并提供一个访问它的全局访问点。
考虑到单例对象内存分配的时机分为懒汉模式,饿汉模式。
另外考虑到线程安全,三种推荐的实现方式:标准,Meyers,Double-Checked-Locking
结论:
如果使用vc6编译器,请放弃设计模式。
如果整个程序是单线程的,那么标准模式或Meyers单例模式是你最佳选择。
如果你使用VC6以后,vc2010以下版本的编译器的话,并且需要线程安全,则使用Double-Checked-Locking版本的单件模式。
尽量避免使用饿汉单例模式,因为具有潜在,不可预测的风险。
标准模式:
"懒汉"单例模式:第一次调用GetInstance()静态方法的时候才进行内存分配.
1) "懒汉"模式虽然有优点,但是每次调用GetInstance()静态方法时,必须判断NULL == m_instance,使程序相对开销增大。
2) 由于使用指针动态内存分配,我们必须在程序结束时,手动的调用ReleaseInstance()静态方法,进行内存的释放。
3) 教科书标准实现最大的缺点是线程不安全。根据该模式的定义,整个应用程序中,不管是单线程,还是多线程,都只能有且只有该类的一个实例。而在多线程中会导致多个实例的产生,从而导致运行代码不正确以及内存的泄露。
标准实现最大的缺点是线程不安全
如:一旦创建多线程中同时请求该单例对象而该类的构造函数耗时较长时,可能在第一个实例未创建完时,发生另外的实例请求,构造时发现NULL == m_instance则执行多次new,析构的时候会出问题,可能造成内存泄漏。
解决方式:在启动时就使用CreateInstance()进行对象的创建,原则上在GetInstance()时就不用判断是否为NULL,但是违背了"懒汉模式"原则(使用时创建)。
Meyers Singleton Pattern:
1、优点:
1)该实现是一个"懒汉"单例模式,意味着只有在第一次调用GetInstance()时才会实例化。
2)不需要每次调用GetInstance()静态方法时,必须判断NULL==m_instance,效率相对高一些。
3)使用对象而不是指针分配内存,因此会自动调用析构函数,不会导致内存泄露。(程序将结束时其析构函数才被执行,但比全局对象的析构函数更早一步执行)。
4)在多线程下的确能够保证有且只有一个实例产生。
2、缺点:
在多线程情况下,并不是真正意义上的线程安全的实现,即该单例对象内存分配了但还未构造完成时,成员未完全初始化时,而另外的线程使用了该成员变量则会导致不一致。
处理方式是:在线程启动时先GetInstance();保证对象提前被完整创建。同样违背了"懒汉原则"。
原因:
这是因为C++中构造函数并不是线程安全的。
C++中的构造函数简单来说分两步:
第一步:内存分配
第二步:初始化成员变量
由于多线程的关系,可能当我们在分配内存好了以后,还没来得急初始化成员变量,就进行线程切换,另外一个线程拿到所有权后,由于内存已经分配了,
但是变量初始化还没进行,因此打印成员变量的相关值会发生不一致现象。
结论:Meyers方式虽然能确保在多线程中产生唯一的实例,但是不能确保成员变量的值是否正确.
需求描述:
1) 是一个"懒汉"单例模式,按需内存分配。
2) 基于模板实现,具有很强的通用性。
3) 自动内存析构,不存在内存泄露问题(使用std::tr1::shared_ptr)。
4) 在多线程情况下,是线程安全的。
5) 尽可能的高效。(线程安全必定涉及到线程同步,线程同步分为内核级别和用户级别的同步对象,用户级别效率远高于内核级别的同步对象,而用户级别效率最高的是InterlockedXXXX系列API)。
6) 这个实际上也是一个Double-Checked Locking实现的单例模式。是传统的Double-Checked-Locking变异版本。