• 【C++】单例模式


    一. 介绍

    单例模式是属于设计模式的一种,那什么是设计模式呢?

    设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法一样。最开始部落之间打仗时都是人拼人的对
    砍;后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法就类似于一种打仗的“设计模式”。

    使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

    一个类只能创建一个对象,即单例模式。该模式可以保证全局(一个进程中)只有一个唯一的实例对象,并提供一个访问它的全局访问点,该实例对象可以被所有线程访问到。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

    单例模式有两种实现:饿汉模式 和 懒汉模式

    二. 饿汉模式

    介绍:就是说不管你将来用或者不用,程序启动时(即 main 函数执行之前)就会创建单例对象。

    特点:如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度会更好。

    实现方法

    • 构造函数私有:防止外部构造单例对象
    • 防拷贝:禁用拷贝构造和赋值运算符重载函数,防止外部拷贝单例类对象
    • 提供接口:在单例类中定义一个自身类的静态对象,这个对象就是单例对象,设计一个静态方法返回这个单例对象的地址给外部。

    通过上面三步就能做到一个类只能创建一个对象,且同一进程下的各线程可以通过一个统一的接口拿到这个实例对象。

    代码示例

    class Singleton
    {
    public:
    	// 提供访问单例对象的接口
    	Singleton* GetInstance()
    	{
    		return &_inst;
    	}
    
    private:
    	// 单例对象
    	static Singleton _inst;
    	// 构造函数私有
    	Singleton();
    	// 防拷贝
    	Singleton(const Singleton&) = delete;
    	Singleton& operator=(Singleton&) = delete;
    };
    
    // 单例对象存储在静态区中
    // 在程序入口之前就完成单例对象的初始化
    Singleton Singleton::_inst;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    补充说明

    在这里插入图片描述

    三. 懒汉模式

    介绍:如果单例对象构造十分耗时或者占用很多系统资源,比如加载数据库啊, 初始化网络连接啊,读取文件啊等等,而且有可能该对象在程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。

    特点:第一次使用实例对象时,才会创建对象,进程启动无负载。且当系统中有多个单例实例时,启动顺序可以自由控制。

    实现方法

    • 防构造:把构造函数私有,这样外部就不能实例化对象
    • 防拷贝:禁用拷贝构造和赋值运算负重载函数,防止外部对我们的单例对象进行拷贝
    • 提供一个GetInstance()接口获取单例对象

    代码示例

    class Singleton
    {
    public:
    	static Singleton* GetInstance()
    	{
    		// 保护第一次需要加锁,后面都不需要加锁的场景,可以使用双检查加锁
    		// 特点:第一次需要加锁,后面不再加锁,保护线程安全,同时提高了效率
    		if (_inst == nullptr)
    		{
    			_mtx.lock();
    			if (_inst == nullptr)
    			{
    				_inst = new Singleton();
    			}
    			_mtx.unlock();
    		}
    		return _inst;
    	}
    
    private:
    	static mutex _mtx;// 互斥锁
    	static Singleton* _inst;// 单例对象指针
    
    	// 防构造(把构造函数设为私有)
    	Singleton() {};
    	// 防拷贝(禁用拷贝构造)
    	Singleton(const Singleton&) = delete;
    	Singleton& operator=(const Singleton&) = delete;// 防拷贝(禁用赋值运算负重载)
    };
    
    // 静态成员初始化
    mutex Singleton::_mtx; 
    Singleton* Singleton::_inst = nullptr;
    
    • 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

    其他版本的懒汉

    利用静态对象的特性:

    • 静态对象的初始化语句只会执行一次
    • 创建一个静态对象的过程是原子的

    这样我们可以不用加锁,直接在接口中创建一个静态单例对象,然后返回单例对象的地址给外部即可。

    class Singleton
    {
    public:
    	static Singleton* GetInstance()
    	{
    		// 静态对象的初始化只会执行一次
    		static Singleton _inst;
    		return &_inst;
    	}
    
    private:
    	Singleton() {};
    	Singleton(const Singleton&) = delete;
    	Singleton& operator=(const Singleton&) = delete;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    缺点:

    • 单例对象创建在静态区,如果单例对象过大,存储在静态区不太合适
    • 因为它在静态区,所以我们无法主动释放单例对象

    四. 饿汉模式和懒汉模式对比

    饿汉

    • 优点:简单
    • 缺点:
      • 如果单例对象构造函数的工作较多,会导致程序启动慢,迟迟进不了入口 main 函数
      • 如果系统中有多个单例对象,且它们之间有初始化的耦合关系,饿汉模式无法控制它们的初始化顺序

    懒汉

    • 优点:需要时才调用,可以达到延迟加载的效果
    • 确定:相对于饿汉,实现起来更复杂,需要加锁和双检查
  • 相关阅读:
    一篇文章让你全方位掌握git版本控制管理
    基于 attention 的蛋白质熔点预测算法研究(l2)
    js简单知识
    0开篇-介绍
    赚够钱回老家吗
    Unity URP
    TDengine 3.0:核心代码全部开源,企业版价值何在?
    今年护网攻防演练目标主域名收集方法汇总
    docker bash: vi: command not found 修改文件无法使用 vi yum的方法
    【前端】IOS微信浏览器点击右上角遮罩实现
  • 原文地址:https://blog.csdn.net/m0_51064412/article/details/133387733