当前位置:   article > 正文

设计模式之单例模式(C++)_单例模式设计c

单例模式设计c

作者:翟天保Steven
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处

一、单例模式是什么?

       单例模式是一种创建型的软件设计模式,在工程项目中非常常见。通过单例模式的设计,使得创建的类在当前进程中只有一个实例,并提供一个全局性的访问点,这样可以规避因频繁创建对象而导致的内存飙升情况。

       实现单例模式的三个要点:

1)私有化构造函数:这样外界就无法自由地创建类对象,进而阻止了多个实例的产生。

2)类定义中含有该类的唯一静态私有对象静态变量存放在全局存储区,且是唯一的,供所有对象使用。

3)用公有的静态函数来获取该实例:提供了访问接口。

       单例模式一般分为懒汉式和饿汉式。

1)懒汉式:在使用类对象(单例实例)时才会去创建它,不然就懒得去搞。

2)饿汉式:单例实例在类装载时构建,有可能全局都没使用过,但它占用了空间,就像等着发救济粮的饿汉提前排好队等吃的一样。

二、懒汉式实现

2.1 懒汉基础实现

       最基本的懒汉实现方法。

  1. //Singleton.h
  2. /****************************************************/
  3. #include <iostream>
  4. using namespace std;
  5. // 单例模式演示类
  6. class Singleton
  7. {
  8. public:
  9. // 公有接口获取唯一实例
  10. static Singleton* getInstance(){
  11. // 若为空则创建
  12. if (instance == nullptr) {
  13. cout << "实例为空,开始创建。" << endl;
  14. instance = new Singleton();
  15. cout << "创建结束。" << endl;
  16. }
  17. else {
  18. cout << "已有实例,返回。" << endl;
  19. }
  20. return instance;
  21. }
  22. private:
  23. // 私有构造函数
  24. Singleton(){
  25. cout << "构造函数启动。" << endl;
  26. };
  27. // 私有析构函数
  28. ~Singleton(){
  29. cout << "析构函数启动。" << endl;
  30. };
  31. private:
  32. // 静态私有对象
  33. static Singleton* instance;
  34. };
  35. // 初始化
  36. Singleton* Singleton::instance = nullptr;
  1. //main.cpp
  2. /****************************************************/
  3. #include <iostream>
  4. #include "Singleton.h"
  5. using namespace std;
  6. int main()
  7. {
  8. cout << "main开始" << endl;
  9. Singleton* s1 = Singleton::getInstance();
  10. Singleton* s2 = Singleton::getInstance();
  11. Singleton* s3 = Singleton::getInstance();
  12. cout << "main结束" << endl;
  13. return 0;
  14. }

       执行代码,让我们看看结果。

       从结果中可以看出这样设计主要有两个问题,一个是线程安全,另一个是内存泄漏。

       线程安全是因为在多线程场景下,有可能出现多个线程同时进行new操作的情况,没通过加锁来限制。

       内存泄漏是因为使用了new在堆上分配了资源,那么在程序结束时,也应该进行delete,确保堆中数据释放。

       接下来,我们先解决线程安全问题,对懒汉式实现进行改进。

2.2 基于双重检测锁的懒汉实现

       通过双重检测锁,可以确保线程安全。

  1. //Singleton.h
  2. /****************************************************/
  3. #include <iostream>
  4. #include <mutex>
  5. using namespace std;
  6. // 单例模式演示类
  7. class Singleton
  8. {
  9. public:
  10. // 公有接口获取唯一实例
  11. static Singleton* getInstance(){
  12. // 若为空则创建
  13. if (instance == nullptr) {
  14. // 加锁保证线程安全
  15. // 如果两个线程同时进行到这一步,一个线程继续向下执行时,另一个线程被堵塞
  16. // 等锁解除后,被堵塞的线程就会跳过下面的if了,因为此时实例已经构建完毕
  17. lock_guard<mutex> l(m_mutex);
  18. if (instance == nullptr) {
  19. cout << "实例为空,开始创建。" << endl;
  20. instance = new Singleton();
  21. cout << "地址为:" << instance << endl;
  22. cout << "创建结束。" << endl;
  23. }
  24. }
  25. else {
  26. cout << "已有实例,返回。" << endl;
  27. }
  28. return instance;
  29. }
  30. private:
  31. // 私有构造函数
  32. Singleton(){
  33. cout << "构造函数启动。" << endl;
  34. };
  35. // 私有析构函数
  36. ~Singleton(){
  37. cout << "析构函数启动。" << endl;
  38. };
  39. private:
  40. // 静态私有对象
  41. static Singleton* instance;
  42. //
  43. static mutex m_mutex;
  44. };
  45. // 初始化
  46. Singleton* Singleton::instance = nullptr;
  47. mutex Singleton::m_mutex;
  1. //main.cpp
  2. /****************************************************/
  3. #include <iostream>
  4. #include "Singleton.h"
  5. using namespace std;
  6. int main()
  7. {
  8. cout << "main开始" << endl;
  9. thread t1([] {
  10. Singleton* s1 = Singleton::getInstance();
  11. });
  12. thread t2([] {
  13. Singleton* s2 = Singleton::getInstance();
  14. });
  15. t1.join();
  16. t2.join();
  17. Singleton* s3 = Singleton::getInstance();
  18. cout << "地址为:" << s3 << endl;
  19. cout << "main结束" << endl;
  20. return 0;
  21. }

       执行代码,让我们看看结果。

       这样看来没有问题,那如果取消双重检测锁,在多线程下看看会发生什么。将代码部分函数修改为下。把锁注释掉,再查看地址信息。

  1. // 公有接口获取唯一实例
  2. static Singleton* getInstance(){
  3. // 若为空则创建
  4. if (instance == nullptr) {
  5. // 加锁保证线程安全
  6. // 如果两个线程同时进行到这一步,一个线程继续向下执行时,另一个线程被堵塞
  7. // 等锁解除后,被堵塞的线程就会跳过下面的if了,因为此时实例已经构建完毕
  8. //lock_guard<mutex> l(m_mutex);
  9. if (instance == nullptr) {
  10. cout << "实例为空,开始创建。" << endl;
  11. instance = new Singleton();
  12. cout << "地址为:" << instance << endl;
  13. cout << "创建结束。" << endl;
  14. }
  15. }
  16. else {
  17. cout << "已有实例,返回。" << endl;
  18. }
  19. return instance;
  20. }

       此时结果中可以看出,两个线程进行了两次new操作,但是最后只能捕捉到最后一次new的地址信息了,前面的那个丢失了。。。。。

       这个测试也是让大家直观地感受下双重检测锁的用处。

       接下来,我们再解决内存泄漏(资源释放)问题,对懒汉式实现进行进一步的改进。

2.3 基于双重检测锁和资源管理的懒汉实现

       在2.2的基础上,我们加入资源管理机制,以达到对资源的释放的目的,解决方法有两个:智能指针&静态嵌套类。

2.3.1 智能指针方案

       将实例指针更换为智能指针,另外智能指针在初始化时,还需要人为添加公有的毁灭函数,因为析构函数私有化了。

  1. //Singleton.h
  2. /****************************************************/
  3. #include <iostream>
  4. #include <mutex>
  5. using namespace std;
  6. // 单例模式演示类
  7. class Singleton
  8. {
  9. public:
  10. // 公有接口获取唯一实例
  11. static shared_ptr<Singleton> getInstance(){
  12. // 若为空则创建
  13. if (instance == nullptr) {
  14. // 加锁保证线程安全
  15. // 如果两个线程同时进行到这一步,一个线程继续向下执行时,另一个线程被堵塞
  16. // 等锁解除后,被堵塞的线程就会跳过下面的if了,因为此时实例已经构建完毕
  17. lock_guard<mutex> l(m_mutex);
  18. if (instance == nullptr) {
  19. cout << "实例为空,开始创建。" << endl;
  20. instance.reset(new Singleton(), destoryInstance);
  21. cout << "地址为:" << instance << endl;
  22. cout << "创建结束。" << endl;
  23. }
  24. }
  25. else {
  26. cout << "已有实例,返回。" << endl;
  27. }
  28. return instance;
  29. }
  30. // 毁灭实例
  31. static void destoryInstance(Singleton* x) {
  32. cout << "自定义释放实例" << endl;
  33. delete x;
  34. }
  35. private:
  36. // 私有构造函数
  37. Singleton(){
  38. cout << "构造函数启动。" << endl;
  39. };
  40. // 私有析构函数
  41. ~Singleton(){
  42. cout << "析构函数启动。" << endl;
  43. };
  44. private:
  45. // 静态私有对象
  46. static shared_ptr<Singleton> instance;
  47. //
  48. static mutex m_mutex;
  49. };
  50. // 初始化
  51. shared_ptr<Singleton> Singleton::instance;
  52. mutex Singleton::m_mutex;
  1. //main.cpp
  2. /****************************************************/
  3. #include <iostream>
  4. #include "Singleton.h"
  5. using namespace std;
  6. int main()
  7. {
  8. cout << "main开始" << endl;
  9. thread t1([] {
  10. shared_ptr<Singleton> s1 = Singleton::getInstance();
  11. });
  12. thread t2([] {
  13. shared_ptr<Singleton> s2 = Singleton::getInstance();
  14. });
  15. t1.join();
  16. t2.join();
  17. shared_ptr<Singleton> s3 = Singleton::getInstance();
  18. cout << "地址为:" << s3 << endl;
  19. cout << "main结束" << endl;
  20. return 0;
  21. }

       应用智能指针后,在程序结束时,它自动进行资源的释放,解决了内存泄漏的问题。

2.3.2 静态嵌套类方案

       类中定义一个嵌套类,初始化该类的静态对象,当程序结束时,该对象进行析构的同时,将单例实例也删除了。

  1. //Singleton.h
  2. /****************************************************/
  3. #include <iostream>
  4. #include <mutex>
  5. using namespace std;
  6. // 单例模式演示类
  7. class Singleton
  8. {
  9. public:
  10. // 公有接口获取唯一实例
  11. static Singleton* getInstance() {
  12. // 若为空则创建
  13. if (instance == nullptr) {
  14. // 加锁保证线程安全
  15. // 如果两个线程同时进行到这一步,一个线程继续向下执行时,另一个线程被堵塞
  16. // 等锁解除后,被堵塞的线程就会跳过下面的if了,因为此时实例已经构建完毕
  17. lock_guard<mutex> l(m_mutex);
  18. if (instance == nullptr) {
  19. cout << "实例为空,开始创建。" << endl;
  20. instance = new Singleton();
  21. cout << "地址为:" << instance << endl;
  22. cout << "创建结束。" << endl;
  23. }
  24. }
  25. else {
  26. cout << "已有实例,返回。" << endl;
  27. }
  28. return instance;
  29. }
  30. private:
  31. // 私有构造函数
  32. Singleton() {
  33. cout << "构造函数启动。" << endl;
  34. };
  35. // 私有析构函数
  36. ~Singleton() {
  37. cout << "析构函数启动。" << endl;
  38. };
  39. // 定义一个删除器
  40. class Deleter {
  41. public:
  42. Deleter() {};
  43. ~Deleter() {
  44. if (instance != nullptr) {
  45. cout << "删除器启动。" << endl;
  46. delete instance;
  47. instance = nullptr;
  48. }
  49. }
  50. };
  51. // 删除器是嵌套类,当该静态对象销毁的时候,也会将单例实例销毁
  52. static Deleter m_deleter;
  53. private:
  54. // 静态私有对象
  55. static Singleton* instance;
  56. //
  57. static mutex m_mutex;
  58. };
  59. // 初始化
  60. Singleton* Singleton::instance = nullptr;
  61. mutex Singleton::m_mutex;
  62. Singleton::Deleter Singleton::m_deleter;

      main.h同2.2中的一致,结果如下,可以看出,当嵌套类Deleter对象销毁时,其析构函数执行的实例删除操作也完成了。

2.4 基于局部静态对象的懒汉实现

       C++11后,规定了局部静态对象在多线程场景下的初始化行为,只有在首次访问时才会创建实例,后续不再创建而是获取。若未创建成功,其他的线程在进行到这步时会自动等待。注意C++11前的版本不是这样的。

       因为有上述的改动,所以出现了一种更简洁方便优雅的实现方法,基于局部静态对象实现。

  1. //Singleton.h
  2. /****************************************************/
  3. #include <iostream>
  4. #include <mutex>
  5. using namespace std;
  6. // 单例模式演示类
  7. class Singleton
  8. {
  9. public:
  10. // 公有接口获取唯一实例
  11. static Singleton& getInstance() {
  12. cout << "获取实例" << endl;
  13. static Singleton instance;
  14. cout << "地址为:" << &instance << endl;
  15. return instance;
  16. }
  17. private:
  18. // 私有构造函数
  19. Singleton() {
  20. cout << "构造函数启动。" << endl;
  21. };
  22. // 私有析构函数
  23. ~Singleton() {
  24. cout << "析构函数启动。" << endl;
  25. };
  26. };
  1. //main.cpp
  2. /****************************************************/
  3. #include <iostream>
  4. #include "Singleton.h"
  5. using namespace std;
  6. int main()
  7. {
  8. cout << "main开始" << endl;
  9. thread t1([] {
  10. Singleton &s1 = Singleton::getInstance();
  11. });
  12. thread t2([] {
  13. Singleton &s2 = Singleton::getInstance();
  14. });
  15. t1.join();
  16. t2.join();
  17. cout << "main结束" << endl;
  18. return 0;
  19. }

       从结果中可以看出,构造函数启动了一次,另一个线程直接获取了地址。并且当程序结束时,进行了自动释放。

三、饿汉式实现

3.1 饿汉基础实现

       饿汉和懒汉的差别就在于,饿汉提前进行了创建,所以它的基础实现也不是很复杂,如下所示。

  1. //Singleton.h
  2. /****************************************************/
  3. #include <iostream>
  4. #include <mutex>
  5. using namespace std;
  6. // 单例模式演示类
  7. class Singleton
  8. {
  9. public:
  10. // 公有接口获取唯一实例
  11. static Singleton* getInstance() {
  12. cout << "获取实例" << endl;
  13. cout << "地址为:" << instance << endl;
  14. return instance;
  15. }
  16. private:
  17. // 私有构造函数
  18. Singleton() {
  19. cout << "构造函数启动。" << endl;
  20. };
  21. // 私有析构函数
  22. ~Singleton() {
  23. cout << "析构函数启动。" << endl;
  24. };
  25. private:
  26. // 静态私有对象
  27. static Singleton* instance;
  28. };
  29. // 初始化
  30. Singleton* Singleton::instance = new Singleton();
  1. //main.cpp
  2. /****************************************************/
  3. #include <iostream>
  4. #include "Singleton.h"
  5. using namespace std;
  6. int main()
  7. {
  8. cout << "main开始" << endl;
  9. thread t1([] {
  10. Singleton* s1 = Singleton::getInstance();
  11. });
  12. thread t2([] {
  13. Singleton* s2 = Singleton::getInstance();
  14. });
  15. t1.join();
  16. t2.join();
  17. cout << "main结束" << endl;
  18. return 0;
  19. }

       输出结果中可知,main还没开始,实例就已经构建完毕,获取实例的函数也不需要进行判空操作,因此也就不用双重检测锁来保证线程安全了,它本身已经是线程安全状态了。

       但是内存泄漏的问题还是要解决的,这点同懒汉是一样的。

3.2 基于资源管理的饿汉实现

       内存泄漏解决方法有两个:智能指针&静态嵌套类。

3.2.1 智能指针方案

       将实例指针更换为智能指针,另外智能指针在初始化时,还需要人为添加公有的毁灭函数,因为析构函数私有化了。

  1. //Singleton.h
  2. /****************************************************/
  3. #include <iostream>
  4. #include <mutex>
  5. using namespace std;
  6. // 单例模式演示类
  7. class Singleton
  8. {
  9. public:
  10. // 公有接口获取唯一实例
  11. static shared_ptr<Singleton> getInstance() {
  12. cout << "获取实例" << endl;
  13. cout << "地址为:" << instance << endl;
  14. return instance;
  15. }
  16. // 毁灭实例
  17. static void destoryInstance(Singleton* x) {
  18. cout << "自定义释放实例" << endl;
  19. delete x;
  20. }
  21. private:
  22. // 私有构造函数
  23. Singleton() {
  24. cout << "构造函数启动。" << endl;
  25. };
  26. // 私有析构函数
  27. ~Singleton() {
  28. cout << "析构函数启动。" << endl;
  29. };
  30. private:
  31. // 静态私有对象
  32. static shared_ptr<Singleton> instance;
  33. };
  34. // 初始化
  35. shared_ptr<Singleton> Singleton::instance(new Singleton(), destoryInstance);
  1. //main.cpp
  2. /****************************************************/
  3. #include <iostream>
  4. #include "Singleton.h"
  5. using namespace std;
  6. int main()
  7. {
  8. cout << "main开始" << endl;
  9. thread t1([] {
  10. shared_ptr<Singleton> s1 = Singleton::getInstance();
  11. });
  12. thread t2([] {
  13. shared_ptr<Singleton> s2 = Singleton::getInstance();
  14. });
  15. t1.join();
  16. t2.join();
  17. cout << "main结束" << endl;
  18. return 0;
  19. }

       加入了智能指针后,不出意外地进行了自动的资源释放。

3.2.2 静态嵌套类方案

       类中定义一个嵌套类,初始化该类的静态对象,当程序结束时,该对象进行析构的同时,将单例实例也删除了。

  1. //Singleton.h
  2. /****************************************************/
  3. #include <iostream>
  4. #include <mutex>
  5. using namespace std;
  6. // 单例模式演示类
  7. class Singleton
  8. {
  9. public:
  10. // 公有接口获取唯一实例
  11. static Singleton* getInstance() {
  12. cout << "获取实例" << endl;
  13. cout << "地址为:" << instance << endl;
  14. return instance;
  15. }
  16. private:
  17. // 私有构造函数
  18. Singleton() {
  19. cout << "构造函数启动。" << endl;
  20. };
  21. // 私有析构函数
  22. ~Singleton() {
  23. cout << "析构函数启动。" << endl;
  24. };
  25. // 定义一个删除器
  26. class Deleter {
  27. public:
  28. Deleter() {};
  29. ~Deleter() {
  30. if (instance != nullptr) {
  31. cout << "删除器启动。" << endl;
  32. delete instance;
  33. instance = nullptr;
  34. }
  35. }
  36. };
  37. // 删除器是嵌套类,当该静态对象销毁的时候,也会将单例实例销毁
  38. static Deleter m_deleter;
  39. private:
  40. // 静态私有对象
  41. static Singleton* instance;
  42. };
  43. // 初始化
  44. Singleton* Singleton::instance = new Singleton();
  45. Singleton::Deleter Singleton::m_deleter;
  1. //main.cpp
  2. /****************************************************/
  3. #include <iostream>
  4. #include "Singleton.h"
  5. using namespace std;
  6. int main()
  7. {
  8. cout << "main开始" << endl;
  9. thread t1([] {
  10. Singleton* s1 = Singleton::getInstance();
  11. });
  12. thread t2([] {
  13. Singleton* s2 = Singleton::getInstance();
  14. });
  15. t1.join();
  16. t2.join();
  17. cout << "main结束" << endl;
  18. return 0;
  19. }

       同懒汉的一样,不多做阐述。

四、总结

       上述讲了这么多关于单例模式的内容,我尽可能地将测试的结果也同步展示了,目的就是帮助大家更好地理解。文中所有的代码都是完整的,可以直接复制到自己的项目中测试验证下。

       最后,如果说让我选择用什么样的实现,那我选择用局部静态对象的方法,代码简洁,线程安全,内存无泄漏,有什么理由说不呢?除非你是C++11之前的版本。。。。

       如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/在线问答5/article/detail/802060
推荐阅读
  

闽ICP备14008679号