C++ 单例模式学习(Singleton)

最近在学习设计模式,学到单例模式,觉得这一讲讲的挺好的,整理一下,一起学习学习。单例模式可能大家都已经非常熟悉了。

单例模式属于“对象性能”模式

“对象性能”模式

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

典型模式

Singleton

Flyweight(享元模式)

Singleton 单例模式

动机

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

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

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

通过代码示例来看看:

class Singleton{
private:
  // 防止外部构造。
  Singleton() = default;

  // 防止拷贝和赋值。
  Singleton& operator=(const Singleton&) = delete;
  Singleton(const Singleton& singleton2) = delete;
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;
}

但是用锁的代价太高了,因此又出现了Double-Checked Locking Pattern (DCLP),双检查锁

//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
   
    if(m_instance==nullptr){
        Lock lock;
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
    }
    return m_instance;
}

Double-Checked Locking Pattern (DCLP),使用两次判断来解决线程安全问题并且提高效率。但是,在很久之后,发现Double-Checked Locking Pattern (DCLP)实际上也是存在严重的线程安全问题。

Scott Meyers and 和Alexandrescu写的一篇文章里面专门分析了这种解决方案的问题C++ and the Perils of Double-Checked Locking

文章链接如下:

https://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

双检查锁,但由于内存读写reorder不安全

reorder不安全指的是什么呢,我们先看下面代码:

instance_ = new Singleton;

这条语句实际上做了三件事,第一:申请一块内存,第二:调用构造函数,第三:将该对象内存地址赋给instance_。

    if (instance_ == nullptr) {  \\ 语句1
      std::lock_guard lock(mutex_);
      if (instance_ == nullptr) {
        instance_ = new Singleton;  \\ 语句2
      }
    }

但由于编译器可能先将该内存地址赋给instance_,然后再调用构造函数。那么,如果线程A恰好申请完内存,并且将内存地址赋给instance_,但是还没调用构造函数的时候。线程B执行到语句1,判断instance_此时不为空,则返回该变量,然后调用该对象的函数,但是线程A使用的对象还没有进行构造。

不过,现在C++11 提供了解决加锁导致效率问题,如下:

//C++ 11版本之后的跨平台实现 (volatile)
std::atomicSingleton::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);//获取内存fence
    if (tmp == nullptr) {
        std::lock_guard 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);//释放内存fence
            m_instance.store(tmp, std::memory_order_relaxed);
        }
    }
    return tmp;
}

在C++11中还提供一种方法,使得函数可以线程安全的只调用一次。即使用std::call_once和std::once_flag。std::call_once是一种lazy load的很简单易用的机制。

使用std::call_once实现单例

实现代码如下:

#include <iostream>
#include <memory>
#include <mutex>

class Singleton {
public:
  static Singleton& GetInstance() {
    static std::once_flag s_flag;
    std::call_once(s_flag, [&]() {
      instance_.reset(new Singleton);
    });

    return *instance_;
  }
  ~Singleton() = default;

private:
  Singleton() = default;

  Singleton(const Singleton&) = delete;
  Singleton& operator=(const Singleton&) = delete;

private:
  static std::unique_ptr<Singleton> instance_;
};

std::unique_ptr<Singleton> Singleton::instance_;

int main() {
  Singleton& s1 = Singleton::GetInstance();
  Singleton& s2 = Singleton::GetInstance();
  return 0;
}

单例模式的定义

保证一个类仅有一个实例,并提供一个该实例的全局访问点

结构

要点总结

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

lSingleton 模式一般不要支持拷贝构造方法和 clone 接口,因为这有可能导致多个对象实例,违背 Singleton 模式的初衷。

l如何实现多线程环境下安全的 Singleton ,注意对双检查锁的正确实现。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cpp编程小茶馆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值