单例模式是设计模式中最简单、常见的一种。其主要目的是确保整个进程中,只有一个类的实例,并且提供一个统一的访问接口。常用于 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. vue中的v-model 与 .sync

    <input v-model="parentData"> //等同于 <input :value="parentData" @input=&q ...

  2. Oracle_表空间

    Oracle 表空间 在执行具体的操作之前,由于Oracle不允许删除现有临时表空间,所以在删除现有临时表空间时要终止现有的实时会话. 查询Oracle表空间名称,表空间物理文件路径 查询临时表空间: ...

  3. selenium中处理验证码问题1-获取验证码图片

    selenium中处理验证码问题: 验证码: 基本作用:可以实现当前访问页面的数据安全性.还可以减少用户的并发数:实现大流量的分流 类型:1.纯数字.纯字母 2.汉字组合 3.数学运算题 4.滑动 5 ...

  4. 在react中使用wangEditorV5

    wangEditor是基于JavaScript和css的一款web富文本编辑器,是国内比较好用的一款轻量级富文本编辑器,上手简单,易用且开源免费. 官方文档:http://www.wangeditor ...

  5. Visual Studio 2022 不支持 .NET Framework 4.5 项目的解决办法

    概述 升级到Visual Studio  2022后,打开速度快了很多,开发体验也舒服很多.只是使用过程中遇到了一个比较尴尬的问题:默认Visual Studio 2022 不再支持安装 .NET F ...

  6. 创建SVN和设置密码以及SVN自动更新

    重新创建版本库:    svnadmin create /usr/local/svn/month_exam //创建一个svn版本仓库month_exam(month_exam可以随便起名字) cd ...

  7. RDIFramework.NET开发框架用户字典助力Saas数据字典应用

    1.概述 在某些特殊应用(如:SaaS)中,系统内置的字典项有可能不能完全满足用户的需求,他们需要自己定义相应的数据项,我们框架完全支持这类应用,用户字典管理主界面如下图所示. 2.功能展示 需要说明 ...

  8. 3.1 JAVA方法:

    JAVA方法: 何为方法 方法是语句的集合,这个集合执行一个功能 方法包含于类或对象中 方法在程序中被创建,在其他地方被引用 java全是值传递 方法的定义和调用 方法的定义: 修饰符 返回类型 方法 ...

  9. pyhton内置函数

    内置函数 1.type(变量名)-> class 查看变量的数据类型 2.print(self, *args, sep=' ', end='\n', file=None) sep:指定多个参数以 ...

  10. Pillow模块——生成随机验证码

    urls.py path('get_code/',views.get_code), views.py中 from PIL import Image,ImageFont,ImageDraw " ...