• 【C++基础】单例模式


    本文章参考:单例模式 - 巴基速递 | 爱编程的大丙

    什么是单例模式

    在一个项目中,全局范围内,某个类的实例有且仅有一个,通过这个唯一实例向其他模块提供数据的全局访问,这种模式就叫单例模式。单例模式的典型应用就是任务队列。

    如果使用单例模式,首先要保证这个类的实例有且仅有一个。因此,就必须采取一系列的防护措施。涉及一个类多对象操作的函数有以下几个:

    构造函数:创建一个新的对象
    拷贝构造函数:根据已有对象拷贝出一个新的对象
    拷贝赋值操作符重载函数:两个对象之间的赋值

    解决措施如下:

    构造函数私有化,在类内部只调用一次
    拷贝构造函数私有化或者禁用(使用 = delete)
    拷贝赋值操作符重载函数私有化或者禁用

    由于使用者在类外部不能使用构造函数,所以在类内部创建的这个唯一的对象必须是静态的,这样就可以通过类名来访问了,为了不破坏类的封装,我们都会把这个静态对象的访问权限设置为私有的。
    在类中只有它的静态成员函数才能访问其静态成员变量,所以可以给这个单例类提供一个静态函数用于得到这个静态的单例对象。

    饿汉模式

    饿汉模式就是在类加载的时候立刻进行实例化,这样就得到了一个唯一的可用对象

    1. class Singleton
    2. {
    3. public:
    4. Singleton(const Singleton& obj) = delete; //禁止拷贝构造
    5. Singleton& operator=(const Singleton& obj) = delete; //禁止运算符重载
    6. static Singleton* getInstance()
    7. {
    8. return m_obj;
    9. }
    10. private:
    11. Singleton() = default; //默认构造函数为私有
    12. static Singleton* m_obj;
    13. };
    14. Singleton* Singleton::m_obj = new Singleton;
    15. int main()
    16. {
    17. Singleton* m_obj = Singleton::getInstance();
    18. }

    注意:类的静态成员变量在使用之前必须在类的外部进行初始化才能使用

    饿汉模式优点简单、线程安全,但其缺点是可能会浪费一些系统资源(单例对象在应用程序启动时就被创建,即使在某些情况下可能永远不会被使用),不支持延迟加载,不适用于需要根据运行时条件来创建单例对象的情况

    懒汉模式

    懒汉模式是在类加载的时候不去创建这个唯一的实例,而是在需要使用的时候再进行实例化。

    1. class LazySingleton
    2. {
    3. public:
    4. LazySingleton(const LazySingleton& obj) = delete; //禁止拷贝构造
    5. LazySingleton& operator=(const LazySingleton& obj) = delete; //禁止运算符重载
    6. static LazySingleton* getInstance()
    7. {
    8. if (m_obj == nullptr)
    9. m_obj = new LazySingleton;
    10. return m_obj;
    11. }
    12. private:
    13. LazySingleton() = default; //默认构造函数为私有
    14. static LazySingleton* m_obj;
    15. };
    16. LazySingleton* LazySingleton::m_obj = nullptr;

    采用懒汉模式时,在调用getInstance()函数获取单例对象的时候,如果在单线程情况下是没有什么问题的,如果是多个线程,调用这个函数去访问单例对象就有问题了。假设有三个线程同时执行了getInstance()函数,在这个函数内部每个线程都会new出一个实例对象。此时,这个任务队列类的实例对象不是一个而是3个,很显然这与单例模式的定义是相悖的。

    故其懒汉模式存在线程安全问题

    懒汉模式的线程安全问题

    双重检查锁定方法

    最常用的解决方案就是使用互斥锁。可以将创建单例对象的代码使用互斥锁锁住。

    并且在加锁、解锁的代码块外层添加了一个if判断,
    其中外层if判断的目的:
    这样当任务队列的实例被创建出来之后,访问这个对象的线程就不会再执行加锁和解锁操作了
    内层的if判断的目的
    对于第一次创建单例对象的时候线程之间还是具有竞争关系

    1. class LazySingleton
    2. {
    3. public:
    4. LazySingleton(const LazySingleton& obj) = delete; //禁止拷贝构造
    5. LazySingleton& operator=(const LazySingleton& obj) = delete; //禁止运算符重载
    6. static LazySingleton* getInstance()
    7. {
    8. if (m_obj == nullptr) {
    9. std::lock_guard lock(m_mutex); //加锁
    10. if (m_obj == nullptr)
    11. m_obj = new LazySingleton;
    12. }
    13. return m_obj;
    14. }
    15. private:
    16. LazySingleton() = default; //默认构造函数为私有
    17. static LazySingleton* m_obj;
    18. static std::mutex m_mutex;
    19. };
    20. LazySingleton* LazySingleton::m_obj = nullptr;
    静态局部对象

    C++11保证了局部静态变量的初始化在多线程环境中是线程安全的。这是一个简单且安全的单例模式实现方式。 

    1. class LazySingleton
    2. {
    3. public:
    4. LazySingleton(const LazySingleton& obj) = delete; //禁止拷贝构造
    5. LazySingleton& operator=(const LazySingleton& obj) = delete; //禁止运算符重载
    6. static LazySingleton* getInstance()
    7. {
    8. static LazySingleton m_obj;
    9. return m_obj;
    10. }
    11. private:
    12. LazySingleton() = default; //默认构造函数为私有
    13. };

    懒汉模式的缺点是在创建实例对象的时候有安全问题,但这样可以减少内存的浪费(如果用不到就不去申请内存了)。

  • 相关阅读:
    【校招VIP】java开源框架之spark
    2022.7.1 分隔链表
    2024 年如何复用 ChatGPT 从头开始​​快速学习 Python
    Java 日志框架,性能无敌横扫所有对手
    GP db模板、dblink、tablespace、交换分区和数据倾斜
    江涛带你玩STM-CubeMx之OLED使用3线SPI和4线SPI驱动详解
    Qt的环境变量处理与程序发布之间的关系
    【Node.js】官网学习笔记
    [TAPL] 概念笔记
    什么时候用@MapperScan 注解?
  • 原文地址:https://blog.csdn.net/weixin_42809675/article/details/132816876