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

    post请求 语法结构 requests.post(url,data = None,json = None) 参数说明 url:需要爬取的网站的网址 data:请求数据 json:json格式的数据 ...

  2. Sevlet规范:HttpServlet类 和 HttpServletRequest接口 源码解析

    Sevlet规范:HttpServlet类 和 HttpServletRequest接口 源码解析 每博一文案 命运总是不如人愿,但往往是在无数的痛苦总,在重重的矛盾和艰辛中,才是人成熟起来. 你,为 ...

  3. IOC创建对象方式

    IOC创建对象方式 User 类  public class User {  private String name;          public User(String name) {      ...

  4. [云计算]概念辨析:云计算 [IaaS/PaaS/SaaS & 公有云/私有云/混合云]

    1 云计算(Cloud Computing) 1.0 云计算的发展 1.1 概念 "云"实质上就是一个[网络], 狭义上讲,云计算就是一种提供资源的网络,使用者可以随时获取&quo ...

  5. [数据库]MYSQL之InnoDB存储引擎 VS MYISAM存储引擎

    InnoDB VS MYISAM 参考文献 innoDB与MyISAM的区别 - Zhihu

  6. 【开源免费】ChatGPT-Java版SDK重磅更新至1.0.10版,支持Tokens计算,快来一键接入。

    简介 ChatGPT Java版SDK开源地址:https://github.com/Grt1228/chatgpt-java ,目前收获将近1000个star. 有bug欢迎朋友们指出,互相学习,所 ...

  7. 扯下@EventListener这个注解的神秘面纱。

    你好呀,我是歪歪. 前段时间看到同事在项目里面使用了一个叫做 @EventListener 的注解. 在这之前,我知道这个注解的用法和想要达到的目的,但是也仅限于此,其内部工作原理对我来说是一个黑盒, ...

  8. 逍遥自在学C语言 位运算符 "|" 的5种高级用法

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

  9. windows如何拉取一个文件夹下的所有文件名

    问题描述:遇到一个问题,是说一个文件夹下的文件太多了,如何去批量的拉去文件名呢,今天用CMD+DIR的方式拉取 1.文件目录也很深,就从文件导航栏进入CMD窗口 2.在当前目录中输入cmd,然后回车 ...

  10. 从零开始学Vue(二~三)—— Vue 实例 / 模板语法(插值、指令)

    概述 vue.js作为现在笔记热门的JS框架,使用比较简单易上手,也成为很多公司首选的JS框架. 但是对于初学者可能学起来有些麻烦,所以推出<从零开始学Vue>系列博客,本系列计划推出19 ...