c++版本之单例模式

单例模式(Singleton)是指一个类仅有一个实例对象,并且该类提供一个获得该实例对象的全局访问点,它包含三个关键元素:

元素一:提供private类型的构造函数
元素二:提供private类型的的静态成员变量,以保存唯一的实例对象;
元素三:提供获得本类实例的全局访问点GetInstance函数,它是静态类型的

初级版本

根据这三个关键元素可以写出一个基本的单例模式,其代码如下:

class CSinglton
{
private:
    //(1)私有额构造函数
    CSinglton(){}
    //在析构函数中释放实例对象
    ~CSinglton()
    {
        if (pInstance != NULL)
        {
            delete pInstance;
            pInstance = NULL;
        }
    }
public:
    //(3)获得本类实例的唯一全局访问点
    static CSinglton* GetInstance()
    {
        //若实例不存在,则创建实例对象
        if (NULL == pInstance)
        {
            pInstance = new CSinglton();
        }
        //实例已经存在,直接该实例对象
        return pInstance;
    }

private:
    static CSinglton* pInstance;//(2)唯一实例对象
};

//静态成员变量,类外初始化实例对象
CSinglton* CSinglton::pInstance = NULL;

由于CSinglton类的构造函数是私有的,外界无法创建实例对象,只能由类本身来创建;这里由GetInstance()全局访问点来创建唯一的实例对象;

下面我们可以创建测试代码,判断是否符合预期,测试代码如下:

CSinglton *pInstance1 = CSinglton::GetInstance();
CSinglton *pInstance2 = CSinglton::GetInstance();

if (pInstance1 == pInstance2)
{
    cout << "同一个实例" << endl;
} 
else
{
    cout << "是两个实例" << endl;
}

运行结果:

这里写图片描述
从测试结果可以看出,代码运行符合基本预期;

多线程基础版本

如果CSinglton 类运行在多线程环境下,可能会导致该类创建两个实例,并造成内存泄漏,原因分析如下:

比如在线程A和线程B中均第一次调用GetInstance函数,当线程A开始执行new CSinglton()语句时,线程A暂停执行;而此刻线程B获得执行权利,并成功创建了实例对象B;当线程A又获得CPU时间片,则继续执行new CSinglton()语句创建实例对象A,导致实际中创建了两个实例对象,并且实例B的指针值被实例A覆盖,实例B没有被释放,导致内存泄漏;

因此我们可以改进我们的代码,对GetInstance内部增加同步机制,修改代码如下:

class CSinglton
{
private:
    //(1)私有额构造函数
    CSinglton(){}
    ~CSinglton()
    {
        if (pInstance != NULL)
        {
            delete pInstance;
            pInstance = NULL;
        }
    }
public:
    //(3)获得本类实例的唯一全局访问点
    static CSinglton* GetInstance()
    {
        //利用MFC中的CSingleLock完成同步,只有一个线程会进入 
        CSingleLock singleLock(&m_CritSection);
        singleLock.Lock();

        if (NULL == pInstance)
        {
            //若实例不存在,则创建实例对象
            pInstance = new CSinglton();
        }
        singleLock.Unlock();

        return pInstance;
    }

private:
    static CSinglton* pInstance;//(2)唯一实例对象
    static CCriticalSection m_CritSection;
};

//静态成员变量,类外初始化实例对象
CSinglton* CSinglton::pInstance = NULL;
CCriticalSection  CSinglton::m_CritSection;

当一个线程已经位于临界区时,另一个线程将会被阻塞,直到当前线程退出临界区,另一个线程才会进入,就可以解决多线程同步问题;

多线程高效版

在一般的多线程环境下,以上代码就可以保证正确性,但是在高并发、访问量很大的环境下,上述的代码实现将对性能造成很大影响;因为每次调用GetInstance都需要加锁,解锁,比较浪费时间,解决方案是采用双重锁定。GetInstance多线程高效版代码如下:

static CSinglton* GetInstance()
{
     //仅在实例未被创建时加锁,其他时候直接返回
    if (NULL == pInstance)
    {
        CSingleLock singleLock(&m_CritSection);
        singleLock.Lock();
        if (NULL == pInstance)
        {
            //若实例不存在,则创建实例对象
            pInstance = new CSinglton();
        }
        singleLock.Unlock();
    }

    //实例已经存在,直接该实例对象
    return pInstance;
}


以上代码实现过程叫做“Double-Check-Locking(双重锁定)”,即我们不让线程每次都加锁,而仅在实例未被初始化时,才进行线程同步,不仅保证内存不泄漏,还大大提高访问性能;

静态初始化版本

这种版本不仅实现最简单而且还避免多线程下的不安全性,其代码如下:

class CSinglton
{
private:
    CSinglton(){}

    ~CSinglton()
    {
        if (pInstance != NULL)
        {
            delete pInstance;
            pInstance = NULL;
        }
    }

public:
    //获得本类实例的唯一全局访问点
    static CSinglton* GetInstance()
    {
        return pInstance;
    }
private:
    static CSinglton* pInstance;
};

//程序运行初期就创建实例
CSinglton* CSinglton::pInstance = new CSinglton;

饿汉式和懒汉式单例类

在单例模式中,常常提到了饿汉式单例类和懒汉式单例类两个词,上面提到的“初级版本”“多线程基础版本” “多线程高效版”都是懒汉式单例类,而“静态初始化版本”是饿汉式单例类,它们的区别主要如下:

  1. 饿汉式单例就是在程序运行之前就创建实例,不管最终程序中是否用到,都占用资源,形容饥不择食的模样;
  2. 懒汉式单例就是程序在第一次调用全局访问点时才实例对象;
  3. 饿汉式单例不需要考虑多线程的安全性,但是有可能导致资源浪费,懒汉式单例需要采用“双重锁定”才能保证系统性能和正确性;

采用何种方式,我们应该根据实际应用情况区别对待;

参考文章:

http://blog.csdn.net/lovelion/article/details/17517213

原文地址:https://www.cnblogs.com/jinxiang1224/p/8468240.html