前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >由浅入深学习单例模式

由浅入深学习单例模式

原创
作者头像
fieldli
发布2019-01-30 11:11:06
3900
发布2019-01-30 11:11:06
举报
文章被收录于专栏:大块小屋-技术大块小屋-技术

单例的核心,无论初始化多少次,获取到的是同一个对象。

首先想到的是,利用局部static对象特性,产生全局唯一的对象。但是如何来避免更多的对象被实例化出来呢。

我们知道在类的编译时,编译器都会默认生成以下四个函数:构造函数,拷贝构造函数,析构函数以及赋值运算符重载函数。这四个函数默认都是public型的,保证外部能够调用。通过这四个函数,外部可以实例化对象,拷贝对象,析构等等。那很容易想到,把这四个函数设置为private就可以避免类在外部被实例化了。

最后,如何保证线程的安全性呢。在一些编译器中static 局部变量并不是线程安全的?有两种方案,加锁和利用类内static对象在main函数之前初始化来保证线程安全。

其他一些基本知识点:

  • static局部静态变量,是在程序执行到语句的时候才进行初始化,且只初始化一次。
  • 类内的static变量,初始化是在外部进行的。且在main函数之前进行初始化。类内的static无论是对象还是基本类型数据,只是起到声明作用,必须要在类外进行初始化。
  • 局部static对象初始化时,在c++11之前是线程不安全。

围绕上面,单例大概如下几种:饿汉模式、懒汉模式、局部static模式、加锁模式、结合模板的通用单例化模式等。下面依次来介绍:

饿汉模式

饿汉模式是指,无论是否使用,程序启动后,就会把单例实例化出来。

代码语言:txt
复制
//饿汉模式
class CSingleInstanceA
{
private:
    CSingleInstanceA(){
        std::cout<<"CSingleInstanceA construct."<<std::endl;
    };
    ~CSingleInstanceA(){
        std::cout<<"CSingleInstanceA desconstruct."<<std::endl;
    };
    CSingleInstanceA(const CSingleInstanceA& csa){

    };

private:
    static CSingleInstanceA m_instance ;//这里只是声明,怎么保证已经初始化了呢
public:
    static CSingleInstanceA* GetInstance(){  //思考一下这里为什么不能是以对象的形式返回?如果返回对象,对象就在外面,就要在外面来销毁,而析构函数是私有的,所以不能有外面来销毁。如果这里改成返回对象类型,会报析构函数是private的错误
        return &m_instance ;
    };
    void DoSomething()
    {
        std::cout<<"hello A. "<<std::endl;
    }
};
CSingleInstanceA CSingleInstanceA::m_instance;//这里的初始一定要有,这块的初始化一般是放在cpp文件中,其初始化顺序在main函数之前。

懒汉模式

懒汉模式,是指在使用到时,才去初始化,没有用到就不处理。有两种方法。指针版和局部static变量版。

下面先看一下指针版的懒汉模式。

代码语言:txt
复制
class CSingleInstanceB
{
private:
    CSingleInstanceB(){ std::cout<<"construct."<<std::endl;};
    ~CSingleInstanceB(){
        std::cout<<"desconstruct."<<std::endl;
    };
    CSingleInstanceB(const CSingleInstanceB& csb){};
private:
    static  CSingleInstanceB* m_instance ;//static CSingleInstanceB* m_instance = NULL; //这种方式初始化也是不行的 ,因为 const static 的变量才可以。static属于全局的,必须在外部初始化。
public:
    static CSingleInstanceB *GetInstance()
    {
        if(m_instance == NULL)
        {
            m_instance = new CSingleInstanceB();//先分配空间,在调用构造函数
        }
        return m_instance;
    }
};
 CSingleInstanceB* CSingleInstanceB::m_instance = NULL;//指针必须初始化。*/

这个有明显的问题,是new出来的对象没有被释放。借助一个static函数来专门delete 这块内存。改进版如下。

代码语言:txt
复制
class CSingleInstanceB
{
private:
    CSingleInstanceB(){ std::cout<<"construct."<<std::endl;};
    ~CSingleInstanceB(){
        std::cout<<"desconstruct."<<std::endl;
    };
    CSingleInstanceB(const CSingleInstanceB& csb){};
private:
    static  CSingleInstanceB* m_instance ;//static CSingleInstanceB* m_instance = NULL; //这种方式初始化也是不行的 ,因为 const static 的变量才可以。static属于全局的,必须在外部初始化。
public:
    static CSingleInstanceB *GetInstance()
    {
        if(m_instance == NULL)
        {
            m_instance = new CSingleInstanceB();//先分配空间,在调用构造函数
        }
        return m_instance;
    }
    static void Destroy() //改进版加入销毁static类,让外部可以来回收new的资源
    {
        delete CSingleInstanceB::m_instance;
    }
};
CSingleInstanceB* CSingleInstanceB::m_instance = NULL;//指针必须初始化。
//使用方法
CSingleInstanceB::GetInstance();//获取单例
CSingleInstanceB::Destroy();//对单例进行资源释放。

这种方法,获取单例后都要加一句释放语句,写代码中很容易遗忘,且看起来不是那么优雅。怎么避免这个主动释放呢?类对象在销毁时会自动调用析构函数来进行资源回收操作,借用这个特性,加一个内部类来实现形成如下版本:

代码语言:txt
复制
class CSingleInstanceB
{
public:
    class InnerHelper
    {
    public:
        InnerHelper(){
            std::cout<<"InnerHelper construct."<<std::endl;
        };
        ~InnerHelper()
        {
            if(CSingleInstanceB::m_instance != NULL)
            {
                std::cout<<"delete m_instance."<<std::endl;
                delete CSingleInstanceB::m_instance;
            }
        }
    };
private:
    CSingleInstanceB(){
        std::cout<<"construct."<<std::endl;
    };
    ~CSingleInstanceB(){
        std::cout<<"desconstruct."<<std::endl;
    };
    CSingleInstanceB(const CSingleInstanceB& csb){};
private:
    static  CSingleInstanceB* m_instance ;//static CSingleInstanceB* m_instance = NULL; //这种方式初始化也是不行的 ,因为 const static 的变量才可以。static属于全局的,必须在外部初始化。

public:
    static InnerHelper ih;//注意这里只是初始化,static类型的必须在类外面进行显示初始化
    static CSingleInstanceB *GetInstance()
    {
        if(CSingleInstanceB::m_instance == NULL)
        {
            CSingleInstanceB::m_instance = new CSingleInstanceB();//先分配空间,在调用构造函数
        }
        return m_instance;
    }
};

CSingleInstanceB* CSingleInstanceB::m_instance = NULL;//指针必须初始化。
CSingleInstanceB::InnerHelper CSingleInstanceB::ih;//注意使用时,静态局部对象要在外部初始化,否则是无法进入构造和析构函数的。

懒汉模式-- 局部对象

代码语言:txt
复制
class CSingleInstanceC
{

private :
    CSingleInstanceC() {
        std::cout<<"CSingleInstanceC Construct."<<std::endl;
    }
    ~CSingleInstanceC() {
        std::cout<<"CSingleInstanceC DeConstruct."<<std::endl;
    }
    CSingleInstanceC(const CSingleInstanceC& csa){}
public:
    static CSingleInstanceC* GetInstance()
    {
        static CSingleInstanceC m_instance;//第一次使用时才初始化。在有些编译器会加一个变量,判断是否初始化。所以对于局部对象型的是线程不安全的。
        return &m_instance;
    }
};

正如注释里所说,这种方式在有些编译器中是线程不安全的,因为 像这种 static CSingleInstanceC m_instance;编译器 会自动添加一个bool型的static变量来标记是否已经初始化过了。

代码语言:txt
复制
if(bInit)
{
    static CSingleInstanceC m_instance;
}

很明显这里先判断是否初始化,再去初始化,这两步在多线程环境下是不安全的。会出现两个线程同时去实例化对象。当然在c++11中已经保证了线程安全的。懒汉模式都是线程不安全的,怎么保证线程不安全呢。肯定是加锁了。我们来引入一个加锁的懒汉模式。

加锁的方式

代码语言:txt
复制
class Lock
{
public:
    bool DoLock(){};
    bool UnLock(){};
};

class CLockWrapper
{
public :
    Lock m_lock;
public:
    CLockWrapper(Lock& lock):m_lock(lock){
       // m_lock.DoLock();
    };
    bool DoLock()
    {
        return  m_lock.DoLock();
    }
    ~CLockWrapper(){
        m_lock.UnLock();//保证无论如何都能释放锁
    };

};

class CSingleInstanceD
{
private:
    CSingleInstanceD(){ std::cout<<"construct."<<std::endl;};
    ~CSingleInstanceD(){
        std::cout<<"desconstruct."<<std::endl;
    };
    CSingleInstanceD(const CSingleInstanceD& csb){};
private:
    static  CSingleInstanceD* m_instance ;//static CSingleInstanceB* m_instance = NULL; //这种方式初始化也是不行的 ,因为 const static 的变量才可以。static属于全局的,必须在外部初始化。
    static Lock lock;
public:
    static CSingleInstanceD *GetInstance()
    {
        if(m_instance == NULL)
        {
            //有异常的锁
            if(lock.DoLock())
            {
                if(m_instance == NULL)
                {
                    m_instance = new CSingleInstanceD();//先分配空间,在调用构造函数.
                }
                lock.UnLock();//当new抛出异常的时候,锁就会被强占,无法被释放。
            }
            //包了一层的锁。
            CLockWrapper lw(lock);//lockWrapper 的析构函数保证一定能释放锁
            if(lw.DoLock())
            {
                if(m_instance == NULL)
                {
                    m_instance = new CSingleInstanceD();
                }
            }

        }
        return m_instance;
    }
    void DoSomething()
    {
        std::cout<<" DoSomething..."<<std::endl;
    }
};

CSingleInstanceD* CSingleInstanceD::m_instance = NULL;//指针必须初始化。
Lock CSingleInstanceD::lock;

注释已经很清楚了。加锁模式中,通过引入了一个Lock的wrapper类,同样借助了析构函数一定会执行的特性,保证锁一定能被释放。

单例的模板

引入模板来实现一个通用化的单例模式。这里以有锁的懒汉模式为例介绍:

代码语言:txt
复制
template<class T>
class CSingleInstanceE
{
private:
    CSingleInstanceE(){ std::cout<<"construct."<<std::endl;};
    ~CSingleInstanceE(){
        std::cout<<"desconstruct."<<std::endl;
    };
    CSingleInstanceE(const CSingleInstanceE& csb){};
private:
    static  CSingleInstanceE* m_instance ;//static CSingleInstanceB* m_instance = NULL; //这种方式初始化也是不行的 ,因为 const static 的变量才可以。static属于全局的,必须在外部初始化。
    static Lock lock;
    T m_typeinstance;//这里就是包装的模版
public:
    static T* GetInstance()
    {
        if(m_instance == NULL)
        {
            //有异常的锁
            if(lock.DoLock())
            {
                if(m_instance == NULL)
                {
                    m_instance = new CSingleInstanceE();//先分配空间,在调用构造函数.
                }
                lock.UnLock();//当new抛出异常的时候,锁就会被强占,无法被释放。
            }
            //包了一层的锁。
            CLockWrapper lw(lock);//lockWrapper 的析构函数保证一定能释放锁
            if(lw.DoLock())
            {
                if(m_instance == NULL)
                {
                    m_instance = new CSingleInstanceE();
                }
            }

        }
        return &(m_instance->m_typeinstance);//这里就是包装的模版实例
    }
    void DoSomething()
    {
        std::cout<<" DoSomething..."<<std::endl;
    }
};

CSingleInstanceE* CSingleInstanceE::m_instance = NULL;//指针必须初始化。
Lock CSingleInstanceD::lock;

完。以上是关于单例的总结。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 饿汉模式
  • 懒汉模式
  • 懒汉模式-- 局部对象
  • 加锁的方式
  • 单例的模板
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档