C++中的单例模式

Posted Xiao__Tian__

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++中的单例模式相关的知识,希望对你有一定的参考价值。

问题描述:

设计模式总体分为三种类型:创建型、结构型、行为型。每一种类型又囊括了众多的子模式。

其中:

        1.创建型模式包括了单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式

        2.结构型模式包括了适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。

        3.行为型模式包括了模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职              责链模式、访问者模式。


        ★在设计或开发中,有时会遇到一个类只能有一个对象被创建,如果有多个对象的话,可能会导致状态的混乱和不一致的情形,其实对于了解设计模式的人来说一眼能看出这是单例模式的应用场景,但是对于不了解设计模式的人来说,要实现它也并非多难,但是如果在面试中被问到这里时,就不仅仅是写得出写不出的问题了,写出来只能说是比写不出的人优秀一些,但并不意味着能通过,因为面试官往往期待的是精简易懂,鲁棒性高的代码,何况这23种模式中,只有单例模式(Singleton)是唯一一个能用简短的几十行代码完整实现的,所以其难度虽小却不容小觑。


在此,仅列举三种常见C++中单例模式的实现:


★1.懒汉式:

        所谓懒汉就是懒散,既然是只能生成该类的一个实例,那就等到真正需要它时再去创建该对象。其特点是延迟加载,比如配置文件,采用懒汉式的方法,配置文件的实例直到用到的时候才会加载。懒汉式可以看成是以“空间换时间”的思想。

class CSingleton

public:
static CSingleton* GetInstance()

     if ( m_pInstance == NULL )  
         m_pInstance = new CSingleton();
     return m_pInstance;

private:
    CSingleton();
    static CSingleton * m_pInstance;
;

▲注:通过观察会发现,new出来的对象,并未在最终被释放,所以存在内存泄漏的问题。的确,此处并未考虑到析构等情况,这也是在面试中短时间内急于求成,未稍加分析而匆忙写出这种代码的鲁莽者爱犯的错误。针对内存泄漏的问题以下是改进的方式:


class CSingleton  
  
private:  
    CSingleton()  
      
      
    static CSingleton *m_pInstance;  
    class CGarbo   
      
    public:  
        ~CGarbo()  
          
            if(CSingleton::m_pInstance)  
                delete CSingleton::m_pInstance;  
          
    ;  
    static CGarbo Garbo;   
public:  
    static CSingleton * GetInstance()  
      
        if(m_pInstance == NULL)  
            m_pInstance = new CSingleton();  
        return m_pInstance;  
      
;

▲在程序运行结束时,系统会调用CSingleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。这种方法释放唯一对象具有以下特征:
     1.在单例类内部定义专有的嵌套类。
     2.在单例类内定义私有的专门用于释放的静态成员。
     3.利用程序在结束时析构全局变量的特性,选择最终的释放时机。


注:GetInstance()使用懒惰初始化,也就是说它的返回值是当这个函数首次被访问时被创建的。这是一种防弹设计——所有GetInstance()之后的调用都返回相同实例的指针。懒汉式的实现的确简单,但是其存在线程安全问题,只适合在单线程环境下工作。

 


┣┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┫

                                                                                                  什么是线程安全?

        如果一段代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。比如:若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

┣┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿┿




★2.饿汉式:

       所谓饿汉就是不管三七二十一,既然是只能生成该类的一个实例,那就一开始就去创建该对象,每次用到后直接返回即可。与懒汉式恰好相反,由延迟加载变为瞬时加载。饿汉式可以看成是以“时间换空间”的思想。

class CSingleton  
  
private:  
    CSingleton()    
      
      
public:  
    static CSingleton * GetInstance()  
      
        static CSingleton instance;   
        return &instance;  
      
;


▲注:饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变。懒汉式如果在创建实例对象时不加上synchronized则会导致对对象的访问不是线程安全的。




★3.多线程环境单例:

        既然懒汉式存在线程安全问题,所以可以对其进行修改,使其能在多线程环境下也是安全的。

class Singleton

private:
    static Singleton* m_instance;
    Singleton()
public:
    static Singleton* getInstance();
;
 
Singleton* Singleton::getInstance()

    lock();
    if (m_instance == NULL)
    
       m_instance = new Singleton();
    
    unlock();

    return m_instance;


▲注:对于维护线程安全的常见机制,首当其冲的解决办法就是加锁,但是该类中的锁的性能不是很高,因为每次判断m_instance是否为空时总是需要加锁,而加锁本身是一种比较耗时的复杂操作,如果线程量很庞大的话,极易造成线程的阻塞,所以有了下面双重锁的改良版本。

Singleton* Singleton::getInstance()

    if(NULL == m_instance)
    
        Lock();//借用其它类来实现,如boost
        if(NULL == m_instance)
        
            m_instance = new Singleton;
        
        UnLock();
    
    return m_instance;


注:这样只够极低的几率下,通过了if (m_instance == NULL)条件的线程才会有进入锁定临界区的可能性,这种几率还是比较低的,不会阻塞太多的线程,但为了防止一个线程进入临界区创建实例,另外的线程也进去临界区创建实例,又加上了一道防御锁if (m_instance == NULL),这样就确保不会重复创建了。只有要处理的数据过于庞大时,这种加锁方式才会成为程序性能最为严重的“瓶颈”。





以上是关于C++中的单例模式的主要内容,如果未能解决你的问题,请参考以下文章

C++ 线程安全的单例模式总结

C++中的单例模式

用 C++ 编写的单例模式中的一些错误

python中的单例设计模式

面经最频繁的单例模式一文彻底整明白

C++的单例模式与线程安全单例模式(懒汉/饿汉)