• 【设计模式】C++单例模式详解


    单例模式

    ⼀个类仅有⼀个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。

    那么,我们必须保证:该类不能被复制;也不能被公开的创造。

    对于 C++ 来说,它的构造函数,拷贝构造函数和赋值函数都不能被公开调用。

    单例模式又分为 懒汉模式饿汉模式 ,它们之间各有好处:

    1. 懒汉模式的实例在第一次被引用时才进行初始化,支持延迟加载,资源利用效率更高;但是当资源访问频繁时,资源同步问题(加锁、解锁)会限制并发性能,也就是不支持高并发。

    2. 饿汉模式提前初始化实例,启动时间较长;但是可以避免资源同步(加锁、解锁)带来的性能消耗,后续的响应时间更好。

    饿汉模式

    在加载类时,对象实例就被创建并初始化,在程序结束时自动销毁,因此,它是线程安全的。

    //.h文件 
    class Singleton { 
    public:   
        static Singleton* GetInstance(){    
            return _Instance; 
        }
      
    private:   
        Singleton() = default;
        ~Singleton() = default;
        Singleton(const Singleton&) = delete;   
        Singleton& operator = (const Singleton&) = delete; 
    
    private:   
        static Singleton* _Instance; 
    }; 
    
    // .CPP文件 
    Singleton* Singleton::_Instance = nullptr;  // 类外初始化,必须写
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    懒汉模式

    在 C++11 标准中,静态局部变量 的初始化是线程安全的,因此,可以用以下方式来编写“懒汉”模式:

    class Singleton {
    public:
        static Singleton& GetInstance() {
            static Singleton instance;  
            return instance;
        }
    private:
        Singleton() = default;
        ~Singleton() = default;
        Singleton(const Singleton&) = delete;
        Singleton& operator = (const Singleton&) = delete;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    更加传统的写法如下,使用 双重检查锁定 来确保线程安全,通过静态成员函数 Destructor 来解决内存泄漏:

    //代码实例(线程安全) 
    emplate<typename T> 
    class Singleton { 
    public: 
        static T* getInstance() {       
            if (_instance == nullptr) {   
                lock_guard<mutex> lock(_mutex);   
                if (_instance == nullptr) {     // 双重检查锁定, 确保线程安全
                    _instance = new T();
                    atexit(Destructor);     // 在程序退出的时候释放资源
                }
            }   
            return _instance; 
        } 
    private:    
        // 防止外界构造/拷贝/删除对象
        Singleton() = default;     
        ~Singleton() = default;     
        Singleton(const Singleton&) = delete;
        Singleton& operator = (const Singleton&) = delete;
      
        static void Destructor() {
            if (_instance != nullptr) {
                delete _instance;
                _instance = nullptr;
            }
        }
        
        // 静态指针
        static T* _instance; 
        // 互斥锁
        static mutex _mutex;
    }; 
    
    // 初始化静态指针
    template<typename T> 
    T* Singleton<T>::_instance = nullptr;
    
    // 初始化互斥锁
    template<typename T>
    mutex Singleton<T>::_mutex;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    但是,这种写法可能出现以下问题:

    由于 new 操作分为三步:分配内存、返回指针、调用构造函数;如果一个线程在分配内存后返回了指针,但还没有构造对象,另一个线程就尝试访问该对象的情况,就可能会出现未定义行为或错误。

    因此,还是推荐使用“静态局部变量”的写法。

    单例的应用

    需要强调的是,单例只是一种组织全局变量和静态函数的方式

    当我们想要拥有应用于某种全局数据集的功能,并且我们想要重复使用时,单例是非常有用的。比如以下场景:

    • 配置管理;

    • 日志记录;

    • 消息队列;

    • 线程池、连接池、内存池、对象池。

    但是,单例模式是存在弊端的:

    1. 单例模式会隐藏类之间的依赖关系。

    由于单例类不需要显示地创建,也不需要依赖参数传递,在函数中直接调用就好,所以在阅读代码时,需要仔细阅读才能清楚哪些类依赖了单例类。

    1. 单例模式的拓展性较差。

    单例类只能创建一个实例,如果哪天需要在代码中创建多个实例,则需要对代码进行较大的改动。

    以数据库连接池为例,假设一开始我们将其设计为一个单例类,而后我们发现,有些 SQL 语句的执行效率低下,长时间占用连接资源,因此我们希望再创建一个连接池实例,让它专门处理运行速度较慢的 SQL 语句,而此时,单例模式就对代码的拓展性产生了影响。

  • 相关阅读:
    【附源码】Python计算机毕业设计美容美发店会员管理系统
    Python实现单例模式
    【初识JavaSe语法笔记起章】——标识符、关键字、字面常量、数据类型、类型转换与提升、字符串类型
    【深入浅出程序设计竞赛(基础篇)第四章 算法从0开始】
    26 主成分分析
    再不跳槽,就真晚了......
    CMU15445 (Fall 2019) 之 Project#1 - Buffer Pool 详解
    Vue3封装自定义指令和hooks,并发布npm包
    【Call for papers】ICML-2023(CCF-A/人工智能/2023年1月26日截稿)
    基于SpringBoot的在线点餐系统【附源码】
  • 原文地址:https://blog.csdn.net/m0_70195913/article/details/134160857