C++ 单例模式(介绍+实现)

文章目录

  • [一. 设计模式](#一. 设计模式)
  • [二. 单例模式](#二. 单例模式)
  • [三. 饿汉模式](#三. 饿汉模式)
  • [四. 懒汉模式](#四. 懒汉模式)
  • 结束语

一. 设计模式

单例模式是一种设计模式

设计模式(Design Pattern)是一套被反复使用,多数人知晓的,经过分类的,代码设计经验的总结。

为什么要有设计模式

就像人类历史发展会产生兵法,最开始部落之间打仗都是谁人多谁获胜。但后来春秋战国时期,七国之间经常战争,发现战争也是有套路的,后来孙子就总结了《孙子兵法》。设计模式也是如此。

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

二. 单例模式

一个类只能创建一个对象,即单例模式,该模式可以保证系统重该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。

比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理

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

三. 饿汉模式

饿汉模式,是在最开始就创建对象,即main函数开始前,对象就已经存在

要点有以下几个:

  1. 因为全局只能有一个对象,所以需要将构造函数私有化
  2. 内部封装static静态对象指针,然后类外初始化
  3. 提供静态成员函数,返回静态对象指针
  4. 使用互斥锁保证数据读取的线程安全
cpp 复制代码
//饿汉模式
//在main函数调用前对象就存在
class Singleton
{
public:
	//静态成员变量获取对象指针
	static Singleton*GetInstance()
	{
		return _ins;
	}
	//添加数据
	void Add(const char*str)
	{
		_vmtx.lock();

		_v.push_back(str);

		_vmtx.unlock();
	}
	//打印数据
	void Print()
	{
		_vmtx.lock();
		
		for (auto& e : _v)
		{
			cout << e << endl;
		}

		_vmtx.unlock();
	}

private:
	//构造函数私有化
	Singleton()
	{}
private:
	vector<string> _v;//存储数据
	mutex _vmtx;//互斥锁
	static Singleton*_ins;//静态指针
};

//静态成员变量类外初始化
Singleton* Singleton::_ins = new Singleton();

饿汉模式的优点就是相对于懒汉模式,较为简单

缺点是,程序刚开始时就创建,如果对象较大,可能导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定

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

四. 懒汉模式

懒汉模式是在第一次使用实例对象时,才创建对象

如果单例对象构造十分耗时或者占用很多资源,比如加载插件,初始化网络连接,读取文件等,为了不影响程序启动,可以使用懒汉模式(延迟加载)

简易的懒汉模式

cpp 复制代码
class Singleton
{
public:
	//静态成员变量获取对象指针
	static Singleton*GetInstance()
	{
		static Singleton _ins;
		return &_ins;
	}
	//添加数据
	void Add(const char*str)
	{
		_vmtx.lock();

		_v.push_back(str);

		_vmtx.unlock();
	}
	//打印数据
	void Print()
	{
		_vmtx.lock();
		
		for (auto& e : _v)
		{
			cout << e << endl;
		}

		_vmtx.unlock();
	}

private:
	//构造函数私有化
	Singleton()
	{}
private:
	vector<string> _v;//存储数据
	mutex _vmtx;//互斥锁
};

但stactic对象在C++11前无法保证线程安全,C++11后保证static对象初始化时线程安全的


复杂的懒汉模式

要点有如下几个:

  1. 懒汉模式的GetInstance需要有双检查加锁,同时因为是静态成员函数,所以还需要封装一个静态的互斥锁保护
  2. 可以使用DelInstance显示释放,但也可以通过回收机制在程序结束时回收资源。
    做法是内部定义一个类,该类的析构函数会显示调用DelInstance,然后再定义一个全局的回收机制对象,这样程序结束时会自动销毁回收机制对象,同时调用DelInstance
cpp 复制代码
//懒汉模式
//第一次调用GetInstance才有对象
class Singleton
{
public:
	//静态成员变量获取对象指针
	static Singleton*GetInstance()
	{
		//双检查加锁
		if (_ins == nullptr)//提高效率
		{
			_imtx.lock();
			//第一次调用为空才初始化
			if (_ins == nullptr)//线程安全
			{
				_ins = new Singleton();
			}
			_imtx.unlock();
		}

		return _ins;
	}

	//添加数据
	void Add(const char*str)
	{
		_vmtx.lock();

		_v.push_back(str);

		_vmtx.unlock();
	}
	//打印数据
	void Print()
	{
		_vmtx.lock();

		for (auto& e : _v)
		{
			cout << e << endl;
		}

		_vmtx.unlock();
	}

	//销毁
	static void DelInstance()
	{
		_imtx.lock();

		if (_ins)
		{
			delete _ins;
			_ins = nullptr;
		}

		_imtx.unlock();
	}

	//回收机制
	class GC
	{
	public:
		~GC()
		{
			DelInstance();
		}
	};

	static GC _gc;//声明
private:
	//构造函数私有化
	Singleton()
	{
		
	}
private:
	vector<string> _v;//存储数据
	mutex _vmtx;//保护vector的互斥锁
	static Singleton*_ins;//静态指针
	static mutex _imtx;//保护Singleton初始化的互斥锁
};

//静态成员变量类外初始化为空
Singleton* Singleton::_ins = nullptr;
mutex Singleton::_imtx;

//程序结束时,会调用其析构函数,内部再调用DelInstance()
Singleton::GC Singleton::_gc;//定义

结束语

感谢你的阅读

如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。

相关推荐
panxianren4 小时前
Bun 1.4 的 bun test --parallel 将 4 秒的测试套件缩短到约 1 秒
java·javascript
程序喵大人6 小时前
【C++入门】编译链接模型 - 01 一个 C++ 程序是怎样变成可执行文件的
开发语言·c++·编译链接模型
by209997 小时前
vector 的模拟实现:扩容与边界的详细阐述(下)
c++·经验分享·vector·动态数组
可乐鸡翅yeah_7 小时前
FFmpeg 生成 HLS 独立分片 independent‑segments 参数到底有什么用
javascript·ffmpeg·音视频·safari·m3u8
福兮说7 小时前
canvas 旋转图片的六个坑:四角被裁、Math.ceil 多出 1px 黑边、JPG 角发黑、翻转方向反了
前端·javascript·图像处理·canvas
Ai-_Man9 小时前
您您这可以把Grok的多个会话比如说。左侧的多个会话一次性导出吗?不是单条会话里面的多次会对话。AI导出鸭
javascript·人工智能·ai·小程序·电脑
liangshanbo121510 小时前
JavaScript 异步核心主线
javascript·事件循环·异步操作
余额瞒着我当琳10 小时前
红黑树的实现--红黑树的概念、规则、效率、结构、旋转、查找、验证
数据结构·c++·算法
青少儿编程课堂10 小时前
AC自动机多模式串匹配精讲:校园关键词巡检实战解析
c++·python·算法·bfs·信息学竞赛
传人once11 小时前
悬停旋转放大和位移效果如何写
javascript·css·动画