• 单例模式 创建型模式之一


    1.定义

            一句话来说就是,某个类只能有一个实例,提供一个全局的访问点。单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

    2.作用

    1. 节省资源。一个类只有一个实例,不存在多份实例,节省资源。
    2. 方便控制。在一些操作公共资源的场景时,避免了多个对象引起的复杂操作。

    3.应用场景

    1. 数据库连接,一般整个程序里面只需要进行一次数据库连接即可,后续就不需要再关闭后连接了。
    2. 日志:只需要声明一个日志对象,任何时候程序只往一个日志内填写。

    4.两大类型

            单例的种类有哪些懒汉式和饿汉式,它们之间有什么区别?

            懒汉式:指全局的单例实例在第一次被使用时构建。
            饿汉式:全局的单例实例在类装载(ClassLoader)时构建。(饿汉式单例性能优于懒汉式单例)
            懒汉式与饿汉式区别:

    1. 懒汉式默认不会实例化,外部什么时候调用什么时候new。饿汉式在类加载的时候就实例化,并且创建单例对象。
    2. 懒汉式是延时加载,在需要的时候才创建对象,而饿汉式是在虚拟机启动的时候就会创建。
    3. 懒汉式在多线程中是线程不安全的,而饿汉式是不存在多线程安全问题的。

    4.1 懒汉式

    4.1.1 加锁的懒汉式

    1. #include
    2. #include
    3. #include
    4. /// 加锁的懒汉式实现 //
    5. class Singleton
    6. {
    7. public:
    8. // 获取单实例对象
    9. static Singleton* GetInstance();
    10. //释放单实例,进程退出时调用
    11. static void deleteInstance();
    12. // 打印实例地址
    13. void Print();
    14. private:
    15. // 将其构造和析构成为私有的, 禁止外部构造和析构
    16. Singleton();
    17. ~Singleton();
    18. // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
    19. Singleton(const Singleton& signal);
    20. const Singleton& operator=(const Singleton& signal);
    21. private:
    22. // 唯一单实例对象指针
    23. static Singleton* m_SingleInstance;
    24. static std::mutex m_Mutex;
    25. };
    26. //初始化静态成员变量
    27. Singleton* Singleton::m_SingleInstance = nullptr;
    28. std::mutex Singleton::m_Mutex;
    29. // 注意:不能返回指针的引用,否则存在外部被修改的风险!
    30. Singleton* Singleton::GetInstance()
    31. {
    32. // 这里使用了两个 if 判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,
    33. // 避免每次调用 GetInstance的方法都加锁,锁的开销毕竟还是有点大的。
    34. if (m_SingleInstance == nullptr)
    35. {
    36. std::unique_lock lock(m_Mutex); // 加锁
    37. if (m_SingleInstance == nullptr)
    38. {
    39. volatile auto temp = new (std::nothrow) Singleton();
    40. m_SingleInstance = temp;
    41. }
    42. }
    43. return m_SingleInstance;
    44. }
    45. void Singleton::deleteInstance()
    46. {
    47. std::unique_lock lock(m_Mutex); // 加锁
    48. if (m_SingleInstance)
    49. {
    50. delete m_SingleInstance;
    51. m_SingleInstance = nullptr;
    52. }
    53. }
    54. void Singleton::Print()
    55. {
    56. std::cout << "我的实例内存地址是:" << this << std::endl;
    57. }
    58. Singleton::Singleton()
    59. {
    60. std::cout << "构造函数" << std::endl;
    61. }
    62. Singleton::~Singleton()
    63. {
    64. std::cout << "析构函数" << std::endl;
    65. }
    66. int main()
    67. {
    68. Singleton* pSingleton1 = Singleton::GetInstance();
    69. pSingleton1->Print();
    70. Singleton* pSingleton2 = Singleton::GetInstance();
    71. pSingleton2->Print();
    72. }

    4.1.2 智能指针

    1. #include
    2. #include
    3. #include
    4. class Singleton {
    5. public:
    6. static std::shared_ptr GetInstance();
    7. void Print() {
    8. std::cout << "我的实例内存地址是:" << this << std::endl;
    9. }
    10. ~Singleton() {
    11. std::cout << "__PRETTY_FUNCTION__" << std::endl;
    12. }
    13. private:
    14. Singleton() {
    15. std::cout << "__PRETTY_FUNCTION__" << std::endl;
    16. }
    17. };
    18. static std::shared_ptr singleton = nullptr;
    19. static std::mutex singletonMutex;
    20. std::shared_ptr Singleton::GetInstance() {
    21. if (singleton == nullptr) {
    22. std::unique_lock lock(singletonMutex);
    23. if (singleton == nullptr) {
    24. singleton = std::shared_ptr(new Singleton());
    25. }
    26. }
    27. return singleton;
    28. }
    29. int main()
    30. {
    31. std::shared_ptr pSingleton1 = Singleton::GetInstance();
    32. pSingleton1->Print();
    33. std::shared_ptr pSingleton2 = Singleton::GetInstance();
    34. pSingleton2->Print();
    35. }

    4.1.3 静态局部变量的懒汉单例(C++11线程安全)

    1. #include
    2. #include
    3. #include
    4. /// 内部静态变量的懒汉实现 //
    5. class Singleton
    6. {
    7. public:
    8. // 获取单实例对象
    9. static Singleton& GetInstance();
    10. // 打印实例地址
    11. void Print();
    12. private:
    13. // 禁止外部构造
    14. Singleton();
    15. // 禁止外部析构
    16. ~Singleton();
    17. // 禁止外部拷贝构造
    18. Singleton(const Singleton& single) = delete;
    19. // 禁止外部赋值操作
    20. const Singleton& operator=(const Singleton& single) = delete;
    21. };
    22. Singleton& Singleton::GetInstance()
    23. {
    24. /**
    25. * 局部静态特性的方式实现单实例。
    26. * 静态局部变量只在当前函数内有效,其他函数无法访问。
    27. * 静态局部变量只在第一次被调用的时候初始化,也存储在静态存储区,生命周期从第一次被初始化起至程序结束止。
    28. */
    29. static Singleton single;
    30. return single;
    31. }
    32. void Singleton::Print()
    33. {
    34. std::cout << "我的实例内存地址是:" << this << std::endl;
    35. }
    36. Singleton::Singleton()
    37. {
    38. std::cout << "构造函数" << std::endl;
    39. }
    40. Singleton::~Singleton()
    41. {
    42. std::cout << "析构函数" << std::endl;
    43. }
    44. int main()
    45. {
    46. Singleton& pSingleton1 = Singleton::GetInstance();
    47. pSingleton1.Print();
    48. Singleton& pSingleton2 = Singleton::GetInstance();
    49. pSingleton2.Print();
    50. }

            注:静态局部变量的懒汉单例(C++11线程安全)

    4.2 饿汉式

    4.2.1 示例代码-常规策略

    1. #include
    2. #include
    3. #include
    4. // 饿汉实现 /
    5. class Singleton
    6. {
    7. public:
    8. // 获取单实例
    9. static Singleton* GetInstance();
    10. // 释放单实例,进程退出时调用
    11. static void deleteInstance();
    12. // 打印实例地址
    13. void Print();
    14. private:
    15. // 将其构造和析构成为私有的, 禁止外部构造和析构
    16. Singleton();
    17. ~Singleton();
    18. // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
    19. Singleton(const Singleton& signal);
    20. const Singleton& operator=(const Singleton& signal);
    21. private:
    22. // 唯一单实例对象指针
    23. static Singleton* g_pSingleton;
    24. };
    25. // 代码一运行就初始化创建实例 ,本身就线程安全
    26. Singleton* Singleton::g_pSingleton = new (std::nothrow) Singleton();
    27. Singleton* Singleton::GetInstance()
    28. {
    29. return g_pSingleton;
    30. }
    31. void Singleton::deleteInstance()
    32. {
    33. if (g_pSingleton)
    34. {
    35. delete g_pSingleton;
    36. g_pSingleton = nullptr;
    37. }
    38. }
    39. void Singleton::Print()
    40. {
    41. std::cout << "我的实例内存地址是:" << this << std::endl;
    42. }
    43. Singleton::Singleton()
    44. {
    45. std::cout << "构造函数" << std::endl;
    46. }
    47. Singleton::~Singleton()
    48. {
    49. std::cout << "析构函数" << std::endl;
    50. }
    51. int main()
    52. {
    53. Singleton* pSingleton1 = Singleton::GetInstance();
    54. pSingleton1->Print();
    55. Singleton* pSingleton2 = Singleton::GetInstance();
    56. pSingleton2->Print();
    57. }

    4.2.2 示例代码-std::call_once

            使用 C++11 std::call_once 实现单例(C++11线程安全)

    1. #include
    2. #include
    3. #include
    4. class Singleton {
    5. public:
    6. static std::shared_ptr GetInstance();
    7. void Print() {
    8. std::cout << "我的实例内存地址是:" << this << std::endl;
    9. }
    10. ~Singleton() {
    11. std::cout << "__PRETTY_FUNCTION__" << std::endl;
    12. }
    13. private:
    14. Singleton() {
    15. std::cout << "__PRETTY_FUNCTION__" << std::endl;
    16. }
    17. };
    18. static std::shared_ptr singleton = nullptr;
    19. static std::once_flag singletonFlag;
    20. std::shared_ptr Singleton::GetInstance() {
    21. std::call_once(singletonFlag, [&] {
    22. singleton = std::shared_ptr(new Singleton());
    23. });
    24. return singleton;
    25. }
    26. int main()
    27. {
    28. std::shared_ptr pSingleton1 = Singleton::GetInstance();
    29. pSingleton1->Print();
    30. std::shared_ptr pSingleton2 = Singleton::GetInstance();
    31. pSingleton2->Print();
    32. }

    5.总结

    懒汉式的线程安全问题:

            线程安全的问题,当多线程获取单例时有可能引发竞态条件:第一个线程在if中判断 m_pInstance是空的,于是开始实例化单例;同时第2个线程也尝试获取单例,这个时候判断m_pInstance还是空的,于是也开始实例化单例;这样就会实例化出两个对象,这就是线程安全问题的由来; 解决办法:加锁。内存泄漏. 注意到类中只负责new出对象,却没有负责delete对象,因此只有构造函数被调用,析构函数却没有被调用;因此会导致内存泄漏。解决办法: 使用共享指针;由于懒汉模式存在多线程的安全问题,因此会衍生出线程安全的懒汉式其中一种方式就是使用智能指针和线程锁另外一种方式是使用局部静态变量。这种方法又叫做 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++静态变量的生存期 是从声明到程序结束,这也是一种懒汉式。这是最推荐的一种单例实现方式:通过局部静态变量的特性保证了线程安全 , 不需要使用共享指针,代码简洁;注意在使用的时候需要声明单例的引用 Single& 才能获取对象。当然,C++11 后面出现std::call_once 就是解决这个问题,但是没有magic static 方便,所以推荐magic static的使用方法 。

    6.引用

    2.5 万字详解:23 种设计模式 - 知乎 (zhihu.com)

    【C++】C++ 单例模式总结(5种单例实现方法)_单例模式c++实现-CSDN博客
    设计模式——单例模式(懒汉式与饿汉式)详解_懒汉式饿汉式是啥设计模式啊?-CSDN博客

    C++ 单例模式-CSDN博客

     

  • 相关阅读:
    分享125个ASP源码,总有一款适合你
    [docker]笔记-存储管理
    基于Vue和Element UI实现前后端分离和交互
    Leetcode 1658. Minimum Operations to Reduce X to Zero 前缀和数组题
    基于GPT搭建私有知识库聊天机器人(六)仿chatGPT打字机效果
    js常用方法之 slice
    有什么方法才能更好的学习java?
    实验五:面向对象编程实验(3)—多态和接口
    【工作流引擎】Activiti的使用02
    Comparable和Comparator有什么区别?你知道他们和Arrays.sort的关系吗?
  • 原文地址:https://blog.csdn.net/Physics_ITBoy/article/details/133278109