C++ 单例模式的各种坑及最佳实践
2023/6/12 1:22:46
本文主要是介绍C++ 单例模式的各种坑及最佳实践,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
单例模式是设计模式中最简单、常见的一种。其主要目的是确保整个进程中,只有一个类的实例,并且提供一个统一的访问接口。常用于 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 的正确性取决于内存模型,关于这部分的讨论可以参考这两篇文章:
- https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
- https://preshing.com/20130930/double-checked-locking-is-fixed-in-cpp11/
版本 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++ 单例模式的各种坑及最佳实践的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享
- 2024-11-22ansible 的archive 参数是什么意思?-icode9专业技术文章分享
- 2024-11-22ansible 中怎么只用archive 排除某个目录?-icode9专业技术文章分享
- 2024-11-22exclude_path参数是什么作用?-icode9专业技术文章分享
- 2024-11-22微信开放平台第三方平台什么时候调用数据预拉取和数据周期性更新接口?-icode9专业技术文章分享
- 2024-11-22uniapp 实现聊天消息会话的列表功能怎么实现?-icode9专业技术文章分享
- 2024-11-22在Mac系统上将图片中的文字提取出来有哪些方法?-icode9专业技术文章分享
- 2024-11-22excel 表格中怎么固定一行显示不滚动?-icode9专业技术文章分享
- 2024-11-22怎么将 -rwxr-xr-x 修改为 drwxr-xr-x?-icode9专业技术文章分享
- 2024-11-22在Excel中怎么将小数向上取整到最接近的整数?-icode9专业技术文章分享