美文网首页
探究C++单例模式

探究C++单例模式

作者: lwj_ow | 来源:发表于2018-11-28 14:51 被阅读0次

最近又拾起了以前找实习看的东西, 看到了一篇博客写C++单例模式觉得很不错, 但是写的有点点杂乱, 这里我自己再总结一番. 参考的博客链接在这:探究C++单例模式

  1. 饿汉模式
    饿汉模式是指单例的实例在程序运行的一开始就立即被初始化, 简单代码如下所示:
class Singleton
{
public:
    static Singleton& getInstance()
    {
        return m_data;
    }
private:
    static Singleton m_data; //static data member 在类中声明,在类外定义
    Singleton(){}
    Singleton(Singleton const&);            // copy ctor hidden
    Singleton& operator=(Singleton const&); // assign op. hidden
    ~Singleton(){}
};

这个模式有个很明显的问题, 静态成员变量的初始化顺序是无法保证的, 假如有两个单例模式的类ASingleton和BSingleton, 某一天我们打算在BSingleton的构造函数中使用ASingleton的实例, 这个时候, 潜在的问题就出现了, 如果ASingleton的实例在BSingleton使用它之前并没有初始化, 那么ASingleton::getinstance()返回的就是一个未初始化的内存区域, 如果我们要使用这个单例的话, 程序就会不出意外的直接崩溃. 下面给出一个例子来:

class ASingleton
{
public:
    static ASingleton* getInstance()
    {
        return &m_data;
    }
    void do_something()
    {
        cout<<"ASingleton do_something!"<<endl;
    }
protected:
    static ASingleton m_data; //static data member 在类中声明,在类外定义
    ASingleton();
    ASingleton(ASingleton const&);            // copy ctor hidden
    ASingleton& operator=(ASingleton const&); // assign op. hidden
    ~ASingleton() {}
};
class BSingleton
{
public:
    static BSingleton* getInstance()
    {
        return &m_data;
    }
    void do_something()
    {
        cout<<"BSingleton do_something!"<<endl;
    }
protected:
    static BSingleton m_data; //static data member 在类中声明,在类外定义
    BSingleton();
    BSingleton(BSingleton const&);            // copy ctor hidden
    BSingleton& operator=(BSingleton const&); // assign op. hidden
    ~BSingleton() {}
};
ASingleton ASingleton::m_data;
BSingleton BSingleton::m_data;
ASingleton::ASingleton()
{
    cout<<"ASingleton constructor!"<<endl;
    BSingleton::getInstance()->do_something();
}
BSingleton::BSingleton()
{
    cout<<"BSingleton constructor!"<<endl;
}

如果我们运行一下的话, 测试的结果大概是这样:


image.png

可以看到这个顺序实际上是有问题的, 因为BSingleton的初始化是在我们使用BSingleton的实例之后的, 但是之所以能打印出来第二行的文字是因为这个函数实际上没有使用类的成员, 也就是说实际上这个函数是不依赖BSingleton的对象的, 在这里我给个简单的例子, 假设有下面一段代码:

#include <iostream>
using namespace std;
class A
{
    public:
        void print(){cout<<"hello world"<<endl;}
};
int main()
{
    A* a = nullptr;
    a->print();
    return 0;
}

有兴趣的同学可以跑一下这个代码, 看起来是无法运行的, 但是实际上这个代码是可以正确打印出hello world的. 这个和上面的问题本质上一样的.
到这里, 我们可以很明显的看出来, 实际上饿汗模式的写法是有点问题的, 不过如果我们能确保不在其他单例类的构造函数中使用其他单例, 问题也就不存在了, 下面来介绍一种另外常见的单例模式写法.

  1. 懒汉模式
    懒汉当然比较懒, 不到万不得已是不会干活的, 这个代码的思想也是来源于此, 代码如下:
class Singleton
{
public:
    static Singleton* getInstance()
    {
        if(! m_data) m_data = new Singleton();
        return m_data;
    }    
private:
    static Singleton* m_data; //static data member 在类中声明,在类外定义
    Singleton(){}
    ~Singleton(){}
};
Singleton* Singleton::m_data = nullptr; 

这个实现主要有两个问题, 第一个很明显的问题是析构函数里面什么都没做, 这个问题比较好解决, 在析构函数中加上判断就好, 第二个问题是这个代码是线程不安全的, 也就说在getInstance函数中, 如果两个线程同时进入这个方法中, 那么就会构造出两个变量, m_data也会初始化两次, 所以这样肯定是有问题的.
下面是改进的办法:

class CSingleton
{
private:
    CSingleton()   //构造函数是私有的
    {
    }
    CSingleton(const CSingleton &);
    CSingleton & operator = (const CSingleton &);
public:
    static CSingleton & GetInstance()
    {
        static CSingleton instance;   //局部静态变量
        return instance;
    }
};

这个基本是没什么问题了, 再考虑到线程安全以及异常安全的话, 有下面这个版本:

class Lock
{
private:       
    CCriticalSection m_cs;
public:
    Lock(CCriticalSection  cs) : m_cs(cs)
    {
        m_cs.Lock();
    }
    ~Lock()
    {
        m_cs.Unlock();
    }
};
class Singleton
{
private:
    Singleton();
    Singleton(const Singleton &);
    Singleton& operator = (const Singleton &);
public:
    static Singleton *Instantialize();
    static Singleton *pInstance;
    static CCriticalSection cs;
};
Singleton* Singleton::pInstance = 0;
Singleton* Singleton::Instantialize()
{
    if(pInstance == NULL)
    {   //double check
        Lock lock(cs);           //用lock实现线程安全,用资源管理类,实现异常安全
        //使用资源管理类,在抛出异常的时候,资源管理类对象会被析构,析构总是发生的无论是因为异常抛出还是语句块结束。
        if(pInstance == NULL)
        {
            pInstance = new Singleton();
        }
    }
    return pInstance;
}

上面代码稍微值得注意的是在获得Singleton的实例函数中, 有两个判断pInstance是否为nullptr的语句, 第一个是为了减少加锁行为的开销, 第二个是为了线程安全, 这里不再细说.

  1. 终极方案
    上面其实基本上已经把方案给出来了, 但是下面给出的这个方案更简单, 是boost库中的实现, 代码如下:
class Singleton
{
public:
    static Singleton* getInstance()
    {
        static Singleton instance;
        return &instance;
    }
protected:
    struct Object_Creator
    {
        Object_Creator()
        {
            Singleton::getInstance();
        }
    };
    static Object_Creator _object_creator;

    Singleton() {}
    ~Singleton() {}
};
Singleton::Object_Creator Singleton::_object_creator;

这个方案综合了懒汉模式和饿汗模式的优点, 既使用了类静态成员变量, 也用到了静态局部变量, 这个方案也没有前面两个方案的缺点, 显然这个版本已经没有了线程安全的问题, 因为我们使用了静态局部变量, 对于前面ASingleton和BSingleton的例子, 我们增加下面的测试代码:

class ASingleton
{
public:
    static ASingleton* getInstance()
    {
        static ASingleton instance;
        return &instance;
    }
    void do_something()
    {
        cout<<"ASingleton do_something!"<<endl;
    }
protected:
    struct Object_Creator
    {
        Object_Creator()
        {
            ASingleton::getInstance();
        }
    };
    static Object_Creator _object_creator;

    ASingleton();
    ~ASingleton() {}
};
class BSingleton
{
public:
    static BSingleton* getInstance()
    {
        static BSingleton instance;
        return &instance;
    }
    void do_something()
    {
        cout<<"BSingleton do_something!"<<endl;
    }
protected:
    struct Object_Creator
    {
        Object_Creator()
        {
            BSingleton::getInstance();
        }
    };
    static Object_Creator _object_creator;

    BSingleton();
    ~BSingleton() {}
};
ASingleton::Object_Creator ASingleton::_object_creator;
BSingleton::Object_Creator BSingleton::_object_creator;
ASingleton::ASingleton()
{
    cout<<"ASingleton constructor!"<<endl;
    BSingleton::getInstance()->do_something();
}
BSingleton::BSingleton()
{
    cout<<"BSingleton constructor!"<<endl;
}

这次的输出结果就很正常了,如下所示:


image.png

因为在BSingleton::getinstance()的时候初始化了BSingleton的变量, 因此也不存在程序崩掉了的问题了.

总结: 单例模式是一个听起来挺简单, 但是实际上用起来并不简单的东西, 就上面看来, 如果我们只是第一次接触这个模式,大概率我们写出来的单例模式的代码是有问题, 所以这次总结也算是收获颇丰吧.

相关文章

  • 探究C++单例模式

    最近又拾起了以前找实习看的东西, 看到了一篇博客写C++单例模式觉得很不错, 但是写的有点点杂乱, 这里我自己再总...

  • 学而时习之单例模式

    本文主要说明单例模式的概念,应用,以及C++实现。 I、上帝视角看单例模式 1.1 单例模式特点 单例模式需要满足...

  • 单例模式

    单例模式及C++实现代码单例模式4种实现详解 c++11改进我们的模式之改进单例模式 单例模式(Singleton...

  • 单例模式探究

    单例模式的使用场景: 产生某对象会消耗过多的资源,为避免频繁地创建与销毁对象对资源的浪费。如:对数据库的操作、访问...

  • Singleton 单例模式

    搬运自大神博客单例模式(Singleton)及其C++实现 单例模式,在GOF的《设计模式:可复用面向对象软件的基...

  • 【设计模式】单例模式

    单例模式 常用单例模式: 懒汉单例模式: 静态内部类单例模式: Android Application 中使用单例模式:

  • Android设计模式总结

    单例模式:饿汉单例模式://饿汉单例模式 懒汉单例模式: Double CheckLock(DCL)实现单例 Bu...

  • C++懒汉式单例模式遇到多线程

    C++懒汉式单例模式遇到多线程 单例模式是一个创建型设计模式, 就是保证在整个程序运行中仅存在该类的一个实例, 比...

  • 单例模式C++

    单例模式,就说保证一个类仅有一个实例,并提供一个访问它的全局访问点。 单例模式结构图 单例模式基本代码 懒汉式和饿...

  • C++单例模式

    所谓单例模式,就是只允许全局有一个此类生成的对象。在C++中有几种实现,下面一一列举。 1. 最初模式 需要解释的...

网友评论

      本文标题:探究C++单例模式

      本文链接:https://www.haomeiwen.com/subject/itegqqtx.html