设计模式:单例

时间:2021-04-30 21:12:33   收藏:0   阅读:21

为什么会有单例模式?

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

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

 

实现方式

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

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<Singleton> GetInstance() {
        if (m_pInstance == NULL) {
            m_pInstance = shared_ptr<Singleton>(new Singleton());
        }

        return m_pInstance;
    },private:
    Singleton() {}
    
    Singleton(const Singleton& other);
    Singleton& operator=(const Singleton& other);
    
    static shared_ptr<Singleton> m_pInstance;
};

shared_ptr<Singleton> Singleton::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<Singleton> GetInstance() {
        Lock lock;
        if (m_pInstance == NULL) {
            m_pInstance = shared_ptr<Singleton>(new Singleton());
        }

        return m_pInstance;
    }

 

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

双检测锁:

    static shared_ptr<Singleton> GetInstance() 
    {
        if (m_pInstance == NULL) {  // test1
            m_mtx.lock();
            if (m_pInstance == NULL) { // test2               
                m_pInstance = shared_ptr<Singleton>(new Singleton());
            }
            m_mtx.unlock();
        }

        return m_pInstance;
    }

 

 解释:

1. 如果多个线程同时调用GetInstance(),mutex互斥保证只有一个线程进入lock后的代码,其他的线程在lock前等待。

  当第一个线程创建了实例后释放锁,其他线程再次进入lock之后的代码,这时就不需要再次创建了,所以在test2这里要加上一个判断。

2. 对于已经创建好的实例,再次进入GetInstance时只需要返回指针即可,这就是test1进行判断的原因。

 

运行时序(memory-order):

上述双检测锁还是不够完善,因为:

  在多核操作系统下同一句命令可能在底层分为多个命令执行,并且顺序不确定,当多个线程同时访问时可能出现不可预知的结果。

  针对创建Singleton的过程,有可能m_pInstance指针已经被赋值了,但Singleton构造还未完成,此时另一个线程进入GetInstance的函数,判断指针不为空就返回使用还未构造完成的Singleton对象。

可以使用原子操作来避免这种情况。

std::atomic<Singleton *> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton *Singleton::getInstance()
{
    Singleton *tmp = m_instance.load(); // 1
    if (tmp == nullptr)
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load();
        if (tmp == nullptr)
        {
            tmp = new Singleton;
            m_instance.store(tmp); // 2
        }
    }
    return tmp;
}

 

静态初始化器(C++11)

其实还有一种最为简便的实现方式,需要依赖C++11之后的语言特性。

Singleton &Singleton::getInstance()
{
    static Singleton instance;
    return instance;
}

 

原文:https://www.cnblogs.com/dylan-liang/p/14720296.html

评论(0
© 2014 bubuko.com 版权所有 - 联系我们:wmxa8@hotmail.com
打开技术之扣,分享程序人生!