赞
踩
原创文章,转载请注明出处。
目录
单例可能是最简单的一种设计模式,实现方法很多种;同时单例也有其局限性。
本文对C++ 单例的常见写法进行了一个总结, 包括1>懒汉式版本、2>线程安全版本智能指针加锁、3>线程安全版本Magic Static; 按照从简单到复杂,最终回归简单的的方式循序渐进地介绍,并且对各种实现方法的局限进行了简单的阐述,大量用到了C++ 11的特性如智能指针,magic static,线程锁;从头到尾理解下来,对于学习和巩固C++语言特性还是很有帮助的。
单例是设计模式里面的一种,全局有且只有一个类的static实例,在程序任何地方都能够调用到。比如游戏客户端的本地Excel的加载,我们都会格式化成json,我习惯用单例做本地数据的管理。
2.2.1 有缺陷的懒汉式
懒汉式(Lazy-Initialization)的方法是直到使用时才实例化对象,也就说直到调用Instance() 方法的时候才 new 一个单例的对象, 如果不被调用就不会占用内存。如果单线程没有问题,当多线程的时候就会出现不可靠的情况。
- #include <iostream>
- using namespace std;
-
- /*
- * 版本1 SingletonPattern_V1 存在以下两个问题
- *
- * 1. 线程不安全, 非线程安全版本
- * 2. 内存泄露
- */
- class SingletonPattern_V1
- {
- private:
- SingletonPattern_V1() {
- cout << "constructor called!" << endl;
- }
- SingletonPattern_V1(SingletonPattern_V1&) = delete;
- SingletonPattern_V1& operator=(const SingletonPattern_V1&) = delete;
- static SingletonPattern_V1* m_pInstance;
-
- public:
- ~SingletonPattern_V1() {
- cout << "destructor called!" << endl;
- }
- //在这里实例化
- static SingletonPattern_V1* Instance() {
- if (!m_pInstance) {
- m_pInstance = new SingletonPattern_V1();
- }
- return m_pInstance;
- }
- void use() const { cout << "in use" << endl; }
- };
-
- //在类外初始化静态变量
- SingletonPattern_V1* SingletonPattern_V1::m_pInstance = nullptr;
-
- //函数入口
- int main()
- {
- //测试
- SingletonPattern_V1* p1 = SingletonPattern_V1::Instance();
- SingletonPattern_V1* p2 = SingletonPattern_V1::Instance();
-
- system("pause");
- return 0;
- }
执行结果是 constructor called!
可以看到,获取了两次类的实例,构造函数被调用一次,表明只生成了唯一实例,这是个最基础版本的单例实现,他有哪些问题呢?
m_pInstance
是空的,于是开始实例化单例;同时第2个线程也尝试获取单例,这个时候判断m_pInstance
还是空的,于是也开始实例化单例;这样就会实例化出两个对象,这就是线程安全问题的由来; 解决办法:加锁因此,这里提出一个改进的,线程安全的、使用智能指针的实现:
2.2.2 线程安全、内存安全的懒汉式单例 (C++11Shared_ptr,C++11 mutex lock)
- #include <iostream>
- using namespace std;
- #include <memory> // C++11 shared_ptr头文件
- #include <mutex> // C++11 mutex头文件
- /*
- * 版本2 SingletonPattern_V2 解决了V1中的问题
- *
- * 1. 通过加锁让线程安全了
- * 2. 通过智能指针(shareptr 基于引用计数)内存没有泄露了
- */
- class SingletonPattern_V2
- {
- public:
- ~SingletonPattern_V2() {
- std::cout << "destructor called!" << std::endl;
- }
- SingletonPattern_V2(SingletonPattern_V2&) = delete;
- SingletonPattern_V2& operator=(const SingletonPattern_V2&) = delete;
-
- //在这里实例化
- static std::shared_ptr<SingletonPattern_V2> Instance()
- {
- //双重检查锁
- if (m_pInstance == nullptr) {
- std::lock_guard<std::mutex> lk(m_mutex);
- if (m_pInstance == nullptr) {
- m_pInstance = std::shared_ptr<SingletonPattern_V2>(new SingletonPattern_V2());
- }
- }
- return m_pInstance;
- }
-
- private:
- SingletonPattern_V2() {
- std::cout << "constructor called!" << std::endl;
- }
- static std::shared_ptr<SingletonPattern_V2> m_pInstance;
- static std::mutex m_mutex;
- };
-
- //在类外初始化静态变量
- std::shared_ptr<SingletonPattern_V2> SingletonPattern_V2::m_pInstance = nullptr;
- std::mutex SingletonPattern_V2::m_mutex;
-
- int main()
- {
- std::shared_ptr<SingletonPattern_V2> p1 = SingletonPattern_V2::Instance();
- std::shared_ptr<SingletonPattern_V2> p2 = SingletonPattern_V2::Instance();
-
- system("pause");
- return 0;
- }
执行结果是 constructor called! destructor called!
优点
缺点
因此这里还有第三种基于 magic static 达到线程安全的方式
2.2.3 最推荐的懒汉式单例(magic static)——局部静态变量
- #include <iostream>
- using namespace std;
- /*
- * 版本3 SingletonPattern_V3 使用局部静态变量 解决了V2中使用智能指针和锁的问题
- *
- * 1. 代码简洁 无智能指针调用
- * 2. 也没有双重检查锁定模式的风险
- */
- class SingletonPattern_V3
- {
- public:
- ~SingletonPattern_V3() {
- std::cout << "destructor called!" << std::endl;
- }
- SingletonPattern_V3(const SingletonPattern_V3&) = delete;
- SingletonPattern_V3& operator=(const SingletonPattern_V3&) = delete;
- static SingletonPattern_V3& Instance() {
- static SingletonPattern_V3 m_pInstance;
- return m_pInstance;
-
- }
- private:
- SingletonPattern_V3() {
- std::cout << "constructor called!" << std::endl;
- }
- };
-
- int main()
- {
- SingletonPattern_V3& instance_1 = SingletonPattern_V3::Instance();
- SingletonPattern_V3& instance_2 = SingletonPattern_V3::Instance();
-
- system("pa
执行结果是 constructor called! destructor called!
魔法静态变量是C++11的核心语言功能特性,提案:N2660 - Dynamic Initialization and Destruction with Concurrency, 最早在GCC2.3 / Clang2.9 / MSVC19.0等编译器得到支持。
这种方法又叫做 Meyers' SingletonMeyer's的单例, 是著名的写出《Effective C++》系列书籍的作者 Meyers 提出的。所用到的特性是在C++11标准中的Magic Static特性:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。
这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。
C++静态变量的生存期 是从声明到程序结束,这也是一种懒汉式。
这是最推荐的一种单例实现方式:
SingletonPattern_V3&
才能获取对象。谢谢!创作不易,大侠请留步… 动起可爱的双手,来个赞再走呗\(^o^)/~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。