单例模式是设计模式中最简单、常见的一种。其主要目的是确保整个进程中,只有一个类的实例,并且提供一个统一的访问接口。常用于 Logger 类、通信接口类等。

基本原理

限制用户直接访问类的构造函数,提供一个统一的 public 接口获取单例对象。

这会有一个“先有鸡还是先有蛋”的问题:

  • 因为用户无法访问构造函数,所以无法创建对象
  • 因为无法创建对象,所以不能调用普通的 getInstance() 方法来获取单例对象

解决这个问题的方法很简单,将 getInstance() 定义为 static 即可(这也会限制 getInstance() 内只能访问类的静态成员)

注意事项

  • 所有的构造函数是 private 的
  • 拷贝构造、拷贝赋值运算符需要显式删除 =delete,防止编译器自动合成(即使你显式定义了析构函数或拷贝构造/拷贝赋值运算符,编译器依然可能合成拷贝赋值运算符/拷贝构造!新的 C++ 标准已将该行为标记为 deprecated,但为了兼容旧代码,目前 C++23 仍然会合成!后面打算单独用笔记总结一下编译器默认合成的函数)

C++ 单例模式的几种实现方式

版本 1 饿汉式

提前创建单例对象

class Singleton1 {
public:
static Singleton1* getInstance() { return &inst; }
Singleton1(const Singleton1&) = delete;
Singleton1& operator=(const Singleton1&) = delete; private:
Singleton1() = default;
static Singleton1 inst;
}; Singleton1 Singleton1::inst;

这个版本在程序启动时创建单例对象,即使没有使用也会创建,浪费资源。

版本 2 懒汉式

版本 2 通过将单例对象的实例化会推迟到首次调用 getInstance(),解决了上面的问题。

class Singleton2 {
public:
static Singleton2* getInstance() {
if (!pSingleton) {
pSingleton = new Singleton2();
}
return pSingleton;
}
Singleton2(const Singleton2&) = delete;
Singleton2& operator=(const Singleton2&) = delete; private:
Singleton2() = default;
static Singleton2* pSingleton;
}; Singleton2* Singleton2::pSingleton = nullptr;

版本 3 线程安全

在版本 2 中,如果多个线程同时调用 getInstance() 则有可能创建多个实例。

class Singleton3 {
public:
static Singleton3* getInstance() {
lock_guard<mutex> lck(mtx);
if (!pSingleton) {
pSingleton = new Singleton3();
}
return pSingleton;
}
Singleton3(const Singleton3&) = delete;
Singleton3& operator=(const Singleton3&) = delete; private:
Singleton3() = default;
static Singleton3* pSingleton;
static mutex mtx;
}; Singleton3* Singleton3::pSingleton = nullptr;
mutex Singleton3::mtx;

加锁可以解决线程安全的问题,但版本 3 的问题在于效率太低。每次调用 getInstance() 都需要加锁,而加锁的开销又是相当的高昂的。

版本 4 DCL (Double-Checked Locking)

版本 4 是版本 3 的改进版本,只有在指针为空的时候才会进行加锁,然后再次判断指针是否为空。而一旦首次初始化完成之后,指针不为空,则不再进行加锁。既保证了线程安全,又不会导致后续每次调用都产生锁的开销。

class Singleton4 {
public:
static Singleton4* getInstance() {
if (!pSingleton) {
lock_guard<mutex> lck(mtx);
if (!pSingleton) {
pSingleton = new Singleton4();
}
}
return pSingleton;
}
Singleton4(const Singleton4&) = delete;
Singleton4& operator=(const Singleton4&) = delete; private:
Singleton4() = default;
static Singleton4* pSingleton;
static mutex mtx;
}; Singleton4* Singleton4::pSingleton = nullptr;
mutex Singleton4::mtx;

DCL 在很长一段时间内被认为是 C++ 单例模式的最佳实践。但也有文章表示 DCL 的正确性取决于内存模型,关于这部分的讨论可以参考这两篇文章:

版本 5 Meyers’ Singleton

这个版本利用局部静态变量来实现单例模式。最早由 C++ 大佬、Effective C++ 系列的作者 Scott Meyers 提出,因此也被称为 Meyers’ Singleton

"This approach is founded on C++'s guarantee that local static objects are initialized when the object's definition is first encountered during a call to that function." ... "As a bonus, if you never call a function emulating a non-local static object, you never incur the cost of constructing and destructing the object."

—— Scott Meyers

TLDR:这就是 C++11 之后的单例模式最佳实践,没有之一。

  • 最简洁:不需要额外定义类的静态成员
  • 线程安全,不需要额外加锁
  • 没有烦人的指针
class Singleton5 {
public:
static Singleton5& getInstance() {
static Singleton5 inst;
return inst;
} Singleton5(const Singleton5&) = delete;
Singleton5& operator=(const Singleton5&) = delete; private:
Singleton5() = default;
};

我曾见到过有人画蛇添足地返回单例指针,就像下面这样

static Singleton* getInstance() {
static Singleton inst;
return &inst;
}

如果没什么正当的理由(我也实在想不到有什么理由),还是老老实实地返回引用吧。现代 C++ 应当避免使用裸指针,关于这一点,我也有一篇笔记:裸指针七宗罪

C++ 单例模式的各种坑及最佳实践的更多相关文章

  1. 【wif系列】C#之单例模式(Singleton Pattern)最佳实践

    目录 前言 单例基类 单例提供者 总结 前言 在上一篇译文--<深入理解C#--在C#中实现单例模式>中,对在C#中实现单例模式进行了详细阐述.我们在日常的开发中可以采用解决方案4或解决方 ...

  2. Java单例模式的最佳实践?

    "读过书,--我便考你一考.茴香豆的茴字,怎样写的?"--鲁迅<孔乙己> 0x00 大纲 目录 0x00 大纲 0x01 前言 0x02 单例的正确性 new关键字 c ...

  3. 《AngularJS深度剖析与最佳实践》简介

    由于年末将至,前阵子一直忙于工作的事务,不得已暂停了微信订阅号的更新,我将会在后续的时间里尽快的继续为大家推送更多的博文.毕竟一个人的力量微薄,精力有限,希望大家能理解,仍然能一如既往的关注和支持sh ...

  4. 快速web开发中的前后端框架选型最佳实践

    这个最佳实践是我目前人在做的一个站点,主要功能: oauth登录 发布文章(我称为"片段"),片段可以自定义一些和内容有关的指标,如“文中人物:12”.支持自定义排版.插图.建立相 ...

  5. H5全屏滚动专题页最佳实践

    1.slip.js + rem.js 主要插件: slip.js github: https://github.com/binnng/slip.js rem.js 插件为阿里淘宝的 rem 实现的基础 ...

  6. fir.im Weekly - 2016 年 Android 最佳实践列表

    2016 年已经过去一半,你在年初制定的成长计划都实现了吗? 学海无涯,技术成长不是一簇而就的事情.本期 fir.im Weekly 推荐 王下邀月熊_Chevalier的 我的编程之路--知识管理与 ...

  7. vue.js+boostrap最佳实践

    一.为什么要写这篇文章 最近忙里偷闲学了一下vue.js,同时也复习了一下boostrap,发现这两种东西如果同时运用到一起,可以发挥很强大的作用,boostrap优雅的样式和丰富的组件使得页面开发变 ...

  8. JQuery高性能最佳实践

    [使用最佳选择器] 使用JQuery时,你可以使用多种选择器,选择同一个元素,各种方法之间的性能是不一样的,有时候差异会特别大. 通常比较常用的选择器有以下几个: ID选择器 $("#id& ...

  9. mongodb 最佳实践

    MongoDB功能预览:http://pan.baidu.com/s/1k2UfW MongoDB在赶集网的应用:http://pan.baidu.com/s/1bngxgLp MongoDB在京东的 ...

  10. 【转】Cocos2d-x下Lua调用自定义C++类和函数的最佳实践

    转自:http://segmentfault.com/blog/hongliang/1190000000631630 关于cocos2d-x下Lua调用C++的文档看了不少,但没有一篇真正把这事给讲明 ...

随机推荐

  1. PTA题目总结

    (1)前言:第一次题目集主要考察JAVA的一些语法知识,比如,控制台的输入,输出时保留两位小数,数组的使用,第十题有点难度,当时没写出来,现在想想 也还好,就是读懂题目有点费劲,第一次题目的题量比较大 ...

  2. ACM-学习记录-数论

    GCD,LCM 定理 a.b两个数的最大公约数乘以它们最小公倍数等于a和b的乘积 axb=GCD(a,b)xLCM(a,b) 据此定理,求3与8的最小公倍数可以为:LCM(3,8)=3x8divGCD ...

  3. R语言文本数据挖掘(二)

    tm文本挖掘示例 文本挖掘是从非结构化的文本信息中抽取潜在的.用户感兴趣的重要模式或知识的过程,可以把它看作数据挖掘或数据库中知识发现的延伸.对文本信息的挖掘主要是以数理统计学和计算语言学为理论基础, ...

  4. 【算法总结】强化学习部分基础算法总结(Q-learning DQN PG AC DDPG TD3)

    总结回顾一下近期学习的RL算法,并给部分实现算法整理了流程图.贴了代码. 1. value-based 基于价值的算法 基于价值算法是通过对agent所属的environment的状态或者状态动作对进 ...

  5. 手写Mybatis代码实现会出现的问题

    实现自定义框架过程中遇到的问题及解决方案: 1.执行 Resources.class.getClassLoader().getResourceAsStream(path) 方法无法获得去字节输入流 解 ...

  6. [MySQL]SQL条件语句

    IF语句:IF(expr1, trueResultExpr, falseResultExpr) 若expr1 == TRUE, 则:返回值为 trueResultExpr: 若expr1 == FAL ...

  7. 逍遥自在学C语言 | 位运算符&的高级用法

    前言 在上一篇文章中,我们介绍了&运算符的基础用法,本篇文章,我们将介绍& 运算符的一些高级用法. 一.人物简介 第一位闪亮登场,有请今后会一直教我们C语言的老师 -- 自在. 第二位 ...

  8. Solon2 的通讯服务线程配置

    Solon 框架,关于通讯服务的所有配置 #服务端口(默认为8080) server.port: 8080 #服务主机(ip) server.host: "0.0.0.0" #服务 ...

  9. DG:三种模式切换

    应用归档日志方式进行数据同步 SQL> alter system set log_archive_dest_2='SERVICE=standby arch noaffirm valid_for= ...

  10. 卸载wamp忘记备份MySql,如何恢复MySql数据

    大家把wamp卸载了,但是数据库忘记备份了.怎么办?不要急,不要慌!打开wamp所在目录(前提是你没有删),你会发现wamp特别良心的帮你把MySql的data文件夹留下来了,这个时候你只要把这个文件 ...