C++ 设计模式 单件模式

以下内容均来自GeekBand极客班C++ 设计模式课程(李建忠老师主讲)

Singleton

面向对象很好地解决了"抽象"的问题,但是必不可免地付出一定的代价,对于通常情况来讲,面向对象的成本大都可以忽略不计。但是某些情况,面向对象所带来的成本必须谨慎处理。

“对象性能”模式

典型模式:

Singleton

Flyweight

动机(Motivation)

在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中存在一个实例,才能确保它们的逻辑正确性,以及良好的效率。

如果绕过常规的构造器,提供一个机制来保证一个类只有一个实例?

这应该是类设计者的责任,而不是使用者的责任。

 

示例

//singleton
class Singleton{
private:
    //构造和拷贝构造都是私有类型
    Singleton();
    Singleton(const Singleton & other);
public:
    static Singleton * getInstance();
    static Singleton * m_instance;
};

Singleton * Singleton::m_instance = nullptr;
//线程非安全版本
Singleton * Singleton::getInstance(){
    if(m_instance == nullptr){
        m_instance = new Singleton();
    }
    return m_instance;
}

因为构造和拷贝构造都是私有,那么说明该对象只能实例化一次

且均该变量一定为静态变量。

在多线程下,下面为不断优化,保证线程安全的各个迭代版本

//直接全局加锁
Singleton * Singleton::getInstance(){
    Lock lock;//函数结束后直接释放
    
    if(m_instance == nullptr){
        m_instance = new Singleton();
    }
    return m_instance;
}

threadA和threadB必定阻塞,多线程安全,但是,影响性能

假设m_instance不是nullptr,多个线程进入,都是读操作而不是写操作,读操作是可以并发的(极高并发,如web服务器,很浪费)

//双检查锁 锁前锁后都检查 double check
Singleton * Singleton::getInstance(){ 
    if(m_instance == nullptr){
        Lock lock;//函数结束后直接释放
        if(m_instance == nullptr){
            //一定要再判读一次,一面thread在进入第一个if后,再次出现被两个线程执行两次
            m_instance = new Singleton();
        }
    }
    //return 读的部分可以并发
    return m_instance;
}

但是以上内容依旧存在漏洞,内存读写会出现reorder的情况

m_instance = new Singleton();
//理论部分:
//先分配内存
//调用构造器
//内存地址给m_instance

//实际部分(有可能):
//先分配内存

//内存地址给m_instance
//调用构造器

当threadA执行到了实际的第二部,threadB进行,执行到了if判断,显然不为nullptr,毕竟已经把内存地址给了

然后threadB去使用这个m_instance,发生错误,因为threadA中并没有调用构造器

//C++ 11版本跨平台实现(volatile)
std::atomic<Singleton> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton * Singleton::getInstance(){
    Singleton * tmp = m_instance->load(std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);//获取内存
    if(tmp == nullptr){
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance->load(std::memory_order_relaxed);
        if(tmp == nullptr){
            tmp = new Singleton;
            std::atomic_thread_fence(std::memory_order_release);//释放内存
            m_instance->store(std::memory_order_relaxed);
            
        }
    }    

    //return 读的部分可以并发
    return tmp;
}

要点总结

Singleton模式中的实例构造器可以设置为protected以允许子类派生。

Singleton模式一般不要支持拷贝构造函数和Clone接口,因为这有可能导致多个对象实例

 

 

 

上一篇:LKJZ25-合并链表(取小的尾插)


下一篇:【UE4 C++】UObject 创建、销毁、内存管理