为什么会有单例模式?

在程序中对于某个类只想有一个对象,并且要限制对象的创建时会用到单例模式。

单例模式实现了:程序全局都可以共享使用一个单例对象,有方便的接口可以获取这个单例对象,禁止创建这个单例类的对象,只能通过设计的接口来使用。

 

实现方式

做到一下几点就可以实现单例模式:

1. 私有化构造函数和析构函数。使得无法随意的创建对象。

2. 对象实例是一个指针,作为这个类的静态成员变量储存。

3. 提供一个共有接口可以获取这个类静态实例成员变量。

 

在第一次使用时初始化:

程序都是不断迭代的过程,我们先写个最基本的单例模式:

// 版本一:最基础版本class Singleton {public:    static Singleton* GetInstance() {        if (m_pInstance == NULL) {
            m_pInstance = new Singleton();
        }        return m_pInstance;
    }private:
    Singleton() {}    ~Singleton() {}
    Singleton(const Singleton& other) {}
    Singleton& operator=(const Singleton& other) {}    static Singleton* m_pInstance;
};

Singleton* Singleton::m_pInstance = nullptr;

 

上个版本的问题是会发生内存泄漏,由于没有释放在堆上申请的内存。

解决的方法有两种:1. 智能指针。 2. 内置删除类

本人更偏向于用智能指针来解决。

智能指针解决内存泄漏

class Singleton {public:    static shared_ptr GetInstance() {        if (m_pInstance == NULL) {
            m_pInstance = shared_ptr(new Singleton());
        }        return m_pInstance;
    },private:
    Singleton() {}
    
    Singleton(const Singleton& other);
    Singleton& operator=(const Singleton& other);    
    static shared_ptr m_pInstance;
};

shared_ptrSingleton::m_pInstance = nullptr;

 本人在尝试这种方案时遇到了几个问题分享下:

1. 新建时智能指针时使用std::make_shared(Singleton)() 会编译报错,跟make_shared内部实现有关还未研究具体原因。

解决方式:

  1)使用上述代码,用临时变量 shared_ptr(new Singleton) 来代替。

     在安全性和效率上make_shared是好于shared_ptr的构造函数的。这种解决方式有一定风险。

  2)

    参考一下大佬的讨论:

  https://stackoverflow.com/questions/8147027/how-do-i-call-stdmake-shared-on-a-class-with-only-protected-or-private-const?rq=1

 2. 析构函数去掉或公有化,因为Singleton对象的析构交给了shared_ptr,必须要公有化析构函数。

 

线程安全性:

该考虑线程安全性了。

也是考虑最简单的方式,价格互斥锁。

    static shared_ptr GetInstance() {
        Lock lock;        if (m_pInstance == NULL) {
            m_pInstance = shared_ptr(new Singleton());
        }        return m_pInstance;
    }

 

因为只是需要防止第一次调用时由于多线程导致的冲突问题,所以这种简单粗暴的加锁方式会影响之后的调用。

双检测锁:

    static shared_ptr GetInstance() 
    {        if (m_pInstance == NULL) {
            m_mtx.lock();            if (m_pInstance == NULL) {                
                m_pInstance = shared_ptr(new Singleton());
            }
            m_mtx.unlock();
        }        return m_pInstance;
    }

 

 解释:

1. 如果多个线程同时调用GetInstance(),mutex互斥下只有一个线程进入new Singleton,确保只有一个实例被创建。当实例被创建后,之前被lock住的线程会再次进入,这时就需要判断是否已经创建过,第二个非空判断的用途。

2. 第一层判断是为了效率,当实例已经被创建后就不需要在进锁了。

但是。。。。。

这种实现在多核系统下依然不安全,原因是:

虽然new singleton是一个语句,但在底层操作系统运行时可能被分为几部分执行,例如,当m_pInstance指针已经被赋值了,但Singleton对象还没有完成构造,这是其他线程再次进入时就有可能使用一个还未完全构造的对象。

 

c++11 的一些特性带来了解决方法: