设计模式(二)
六、单例模式
定义
保证一个类仅有一个实例,并提供一个该实例的全局访问点。 ——《设计模式》GoF
版本一
1 class Singleton { 2 public: 3 static Singleton * GetInstance() { 4 if (_instance == nullptr) { 5 _instance = new Singleton(); 6 } 7 return _instance; 8 } 9 private: 10 Singleton() {} //构造 11 ~Singleton() {} 12 Singleton(const Singleton &clone){} //拷⻉构造 13 Singleton& operator=(const Singleton&) {} 14 static Singleton * _instance; 15 }; 16 Singleton* Singleton::_instance = nullptr;//静态成员需要初始化 17
版本一中,_instance对象是指针静态对象,存储在全局数据区,指针指向的对象空间是通过new得到的,在堆区。在此版本中,在堆区创建了对象,但是没有手动释放,造成了内存泄漏。
版本二
1 // 类对象之间 友元 2 class Singleton { 3 public: 4 static Singleton * GetInstance() { 5 if (_instance == nullptr) { 6 _instance = new Singleton(); 7 atexit(Destructor); 8 } 9 return _instance; 10 }12 private: 13 static void Destructor() { 14 if (nullptr != _instance) { // 15 delete _instance; 16 _instance = nullptr; 17 } 18 } 19 Singleton();//构造 20 ~Singleton() {} 21 Singleton(const Singleton &cpy); //拷⻉构造 22 Singleton& operator=(const Singleton& other) {} 23 static Singleton * _instance; 24 }; 25 Singleton* Singleton::_instance = nullptr;//静态成员需要初始化 26 // 还可以使⽤ 内部类,智能指针来解决; 此时还有线程安全问题 27
版本二使用atexit()函数解决了进程退出后单例中堆内存的释放问题。除了用这种解决方法,我们还可以将new换成智能指针或者内部类的实现了避免手动创建和释放堆内存。但是,在这两个版本中我们都没有考虑多线程临界资源问题。
版本三
1 #include <mutex> 2 class Singleton { // 懒汉模式 lazy load 3 public: 4 static Singleton * GetInstance() { 6 if (_instance == nullptr) { 7 std::lock_guard<std::mutex> lock(_mutex); // 3.2 8 if (_instance == nullptr) { 9 _instance = new Singleton(); 10 atexit(Destructor); 11 } 12 } 13 return _instance; 14 } 15 private: 16 static void Destructor() { 17 if (nullptr != _instance) { 18 delete _instance; 19 _instance = nullptr; 20 } 21 } 22 Singleton(){} //构造 23 Singleton(const Singleton &cpy){} //拷⻉构造 24 Singleton& operator=(const Singleton&) {} 25 static Singleton * _instance; 26 static std::mutex _mutex; 27 }; 28 Singleton* Singleton::_instance = nullptr;//静态成员需要初始化 29 std::mutex Singleton::_mutex; //互斥锁初始化
版本三使用了互斥锁完成了对临界资源的保护,同时,使用双重判断指针是否为空,防止出现在第一次判空后指针被其他线程赋值后本线程再次赋值的重复操作。
对于new操作,一共有三个顺序步骤
- 分配内存
- 调用构造函数
- 返回指针
在多线程环境下,cpu会可能会执行reorder进行优化,即先分配内存,然后就返回指针,最后再调用构造函数。在这种情况下,可能会导致其他线程得到的指针是一个野指针。
版本四
1 // volitile关键字 在java中有内存屏障的作用,在C++中没有 2 #include <mutex> 3 #include <atomic> 4 class Singleton { 5 public: 6 static Singleton * GetInstance() { 7 Singleton* tmp = _instance.load(std::memory_order_relaxed); 8 std::atomic_thread_fence(std::memory_order_acquire);//获取内存屏障 9 if (tmp == nullptr) { 10 std::lock_guard<std::mutex> lock(_mutex); 11 tmp = _instance.load(std::memory_order_relaxed); 12 if (tmp == nullptr) { 13 tmp = new Singleton; 14 std::atomic_thread_fence(std::memory_order_release);//释放内存屏障 16 _instance.store(tmp, std::memory_order_relaxed); 17 atexit(Destructor); 18 } 19 } 20 return tmp; 21 } 22 private: 23 static void Destructor() { 24 Singleton* tmp = _instance.load(std::memory_order_relaxed); 25 if (nullptr != tmp) { 26 delete tmp; 27 } 28 } 29 Singleton(){} 30 Singleton(const Singleton&) {} 31 Singleton& operator=(const Singleton&) {} 32 static std::atomic<Singleton*> _instance; 33 static std::mutex _mutex; 34 }; 35 std::atomic<Singleton*> Singleton::_instance;//静态成员需要初始化 36 std::mutex Singleton::_mutex; //互斥锁初始化 37 // g++ Singleton.cpp -o singleton -std=c++11
版本四中使用原子库来实现对_instance对象的new过程的内存屏障。
版本五
1 // c++11 magic static 特性:如果当变量在初始化的时候,并发同时进⼊声明语句,并发线程将会阻塞等待初始化结束。 2 // c++ effective的作者写的一个单例版本 3 class Singleton 4 { 5 public: 6 static Singleton& GetInstance() { 7 static Singleton instance; 8 return instance; 9 } 10 private: 11 Singleton(){} 12 ~Singleton() {} 13 Singleton(const Singleton&) {} 14 Singleton& operator=(const Singleton&) {} 15 }; 16 // g++ Singleton.cpp -o singleton -std=c++11 17 /*该版本具备的优点: 18 1. 利⽤静态局部变量特性,延迟加载; 19 2. 利⽤静态局部变量特性,系统⾃动回收内存,⾃动调⽤析构函数; 20 3. 静态局部变量初始化时,没有 new 操作带来的cpu指令reorder操作; 21 4. c++11 静态局部变量初始化时,具备线程安全; 22 */ 23
版本五是一个常用版本,但是,单例对象的类型被写死了。
版本六
1 template<typename T> 2 class Singleton { 3 public: 4 static T& GetInstance() { 5 static T instance; // 这⾥要初始化DesignPattern,需要调用DesignPattern构造函数,同时会调⽤⽗类的构造函数。 6 return instance; 7 } 8 protected: 9 virtual ~Singleton() {} 10 Singleton() {} // protected修饰构造函数,才能让别⼈继承 11 Singleton(const Singleton&) {} 12 Singleton& operator =(const Singleton&) {} 13 }; 14 class DesignPattern : public Singleton<DesignPattern> { 15 friend class Singleton<DesignPattern>; // friend 能让Singleton<T> 访问到DesignPattern构造函数 16 public: 17 ~DesignPattern() {} 18 private: 19 DesignPattern() {} 20 DesignPattern(const DesignPattern&) {} 21 DesignPattern& operator=(const DesignPattern&) {} 22 }; 23
版本六将单例对象的类型抽象了出来,使得对象类型可变。
结构图
------------------------------------------------------------------------------------------------------------------------
github:https://github.com/illusorycat/Design-patterns.git
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现