当前位置: 首页 > news >正文

现代C++ 实现单例模式

传统写法有什么问题

如果你了解过单例模式,双重检查锁定模式(Double-Checked Locking Pattern,后文简称DCLP)的写法你一定不会陌生,甚至你或许认为它是最正确的代码。

class Singleton {
public://获取单例Singleton* GetInstance() {//双重检查if (p_instance==nullptr) {_mutex.lock();if (p_instance == nullptr)p_instance = new Singleton();_mutex.unlock();}return p_instance;}
private://私有构造函数Singleton() = default;
public:~Singleton() = default;
private://删除构造方法Singleton& operator=(const Singleton& obj) = delete;Singleton(const Singleton& obj) = delete;Singleton& operator=(const Singleton&& obj) = delete;Singleton(const Singleton&& obj) = delete;
private:static Singleton* p_instance;mutex _mutex;
};
Singleton* Singleton::p_instance = nullptr;//类外初始化

这几乎是最常见的单例模式,不过上面的代码却有大问题

问题就出现在这一句:

				p_instance = new Singleton();

该问题在Effective C++的作者Scott Meyers关于单例的论文中被深刻讨论,如果你之前对单例模式缺乏了解,这篇论文你一定需要阅读:

C++ and the Perils of Double-Checked Lockingicon-default.png?t=N7T8https://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

其中提到,要执行该表达式 p_instance = new Singleton(); 需要做三件事:

  1. 开辟出用于存放单例的内存
  2. 在开辟的内存上构造Singleton对象
  3. 使p_instance指针指向开辟的内存

你期望,按照上述步骤去实例化单例,可是编译器没有任何约束去按你期望的顺序执行!

在特殊情况下,编译器有时会调换步骤二和步骤三,就像如下代码:

	Singleton* GetInstance() {//双重检查if (p_instance==nullptr) {_mutex.lock();if (p_instance == nullptr) {p_instance = static_cast<Singleton*>	 //步骤三(operator new(sizeof(Singleton)));//步骤一new(p_instance) Singleton;			        //步骤二}_mutex.unlock();}return p_instance;}

此时执行顺序变为步骤一,步骤三,步骤二

如果你还没有理解,设想有如下场景:

  • 线程A 进入GetInstance,执行完判断,上锁,执行步骤三和步骤一。此时p_instance已经是一个非空的指针,但Singleton单例还未在p_instance所指向的空间上初始化。
  • 线程B 进入GetInstance,判断p_instance为非空,随后把p_instance返回。调用处对p_instance解引用,直接G!访问未初始化的内存。

DCLP只会在步骤一,步骤二在步骤三之前执行才能奏效,但他们的执行顺序确是未定义行为!编译器在这一点上不受任何约束,完全可能导致意想不到的问题。

所以用DCLP实现的单例模式,单例对象的初始化顺序不确定。这种情况可能导致在一个线程中访问尚未初始化的单例对象,从而引发错误。 并不是线程安全,因为包含未定义行为。

现代C++实现单例模式

为了规避这个问题,可利用C++11引入 魔法静态变量 特性,主要描述了具有静态存储期或线程存储期的块作用域变量的初始化规则。

§6.7 [stmt.dcl] p4原文: If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.

即,如果在变量(具有静态存储期或线程存储期的块作用域的)初始化过程中同时并发地再次进入声明,那么并发执行必须等待初始化过程完成。

§6.7 [stmt.dcl] p4http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf

利用这项标准,你就可以很爽的写出比DCLP更好的代码!

代码样例:

class Singleton {
public://现代写法Singleton& GetInstance() {// Let's create a magic staticstatic Singleton instance;//C++11return instance;//return refence}
private://私有构造函数Singleton() = default;
public:~Singleton() = default;
private://删除构造方法Singleton& operator=(const Singleton& obj) = delete;Singleton(const Singleton& obj) = delete;Singleton& operator=(const Singleton&& obj) = delete;Singleton(const Singleton&& obj) = delete;
};

现代写法使用使用局部静态变量来实现单例模式。具体而言,文章提出的Meyers' Singleton就是基于这一思想的解决方案。

如果本文帮助到你,希望能点赞收藏,欢迎留言讨论。

相关文章:

  • Windows 11上边两个空格导致我多熬了1个多小时
  • TOGAF—架构(Architecture)项目管理
  • npm ,yarn 更换使用国内镜像源,阿里源,清华大学源
  • Tomcat 十大安全优化方法(详解版)
  • 浅析LDPC软解码对SSD延迟的影响-part1
  • 浅入浅出理解MySQL和InnoDB
  • 安装python
  • 【PHP】openssl_encrypt、openssl_decrypt对称加密解密
  • 【ArcGIS微课1000例】0079:ArcGIS Earth根据经纬坐标生成点shapefile
  • 初识Pandas函数是Python的一个库(继续更新...)
  • 在Linux上配置全局HTTP代理的详细步骤
  • Parade Series - Message Interaction
  • 【数据结构】——查找、散列表简答题模板
  • 动态内存管理,malloc和calloc以及realloc函数用法
  • github 学习番外篇
  • @angular/forms 源码解析之双向绑定
  • 2018一半小结一波
  • iOS编译提示和导航提示
  • JS函数式编程 数组部分风格 ES6版
  • Leetcode 27 Remove Element
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • Meteor的表单提交:Form
  • mysql innodb 索引使用指南
  • uva 10370 Above Average
  • 工作中总结前端开发流程--vue项目
  • 简单基于spring的redis配置(单机和集群模式)
  • 设计模式(12)迭代器模式(讲解+应用)
  • 由插件封装引出的一丢丢思考
  • 云大使推广中的常见热门问题
  • 400多位云计算专家和开发者,加入了同一个组织 ...
  • #使用清华镜像源 安装/更新 指定版本tensorflow
  • (2)(2.10) LTM telemetry
  • (3)选择元素——(17)练习(Exercises)
  • (二)fiber的基本认识
  • (非本人原创)史记·柴静列传(r4笔记第65天)
  • (附源码)springboot 智能停车场系统 毕业设计065415
  • (附源码)ssm考生评分系统 毕业设计 071114
  • (规划)24届春招和25届暑假实习路线准备规划
  • (一)基于IDEA的JAVA基础12
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • .NET 4.0中的泛型协变和反变
  • .net core 6 redis操作类
  • .NET Core 将实体类转换为 SQL(ORM 映射)
  • .net core 连接数据库,通过数据库生成Modell
  • .NET Core 通过 Ef Core 操作 Mysql
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .NET 动态调用WebService + WSE + UsernameToken
  • .net安装_还在用第三方安装.NET?Win10自带.NET3.5安装
  • ?php echo ?,?php echo Hello world!;?
  • @Autowired自动装配
  • @column注解_MyBatis注解开发 -MyBatis(15)
  • []我的函数库
  • [14]内置对象
  • [2018][note]用于超快偏振开关和动态光束分裂的all-optical有源THz超表——