【C++】设计模式-单例模式

目录

一、单例模式

单例模式的三个要点

针对上述三要点的解决方案

常用的两类单例模式

 二、懒汉模式实现

1.基本实现

2.锁+静态成员析构单例

3.双层检查锁定优化

4.双层检查锁定+智能指针

三、饿汉模式实现

1.基础实现

2.嵌套内部类解决内存泄漏

3.智能指针解决内存泄漏 


一、单例模式

单例模式(Singleton Pattern)是 一种属于创建型设计模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。(即它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。)

单例模式的三个要点

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

针对上述三要点的解决方案

1)私有化构造函数:这样外界就无法自由地创建类对象,进而阻止了多个实例的产生。

2)类定义中含有该类的唯一静态私有对象:静态变量存放在全局存储区,且是唯一的,供所有对象使用。

3)用公有的静态函数来获取该实例:提供了访问接口。

常用的两类单例模式

1)懒汉模式:在使用类对象(单例实例)时才会去创建它,不然就不创建。

2)饿汉模式:单例实例在类装载时构建,有可能全局都没使用过,但它占用了空间,就像等着发救济粮的饿汉提前排好队等吃的一样。


 二、懒汉模式实现

1.基本实现

//singleton.h
#pragma once
#include <iostream>
using namespace std;

class Singleton
{
public:
	//公共接口获取唯一实例
	static Singleton* getInstance()
	{
		if (m_instance == nullptr)
		{
			cout << "创建实例" << endl;
			m_instance = new Singleton;
		}
		return m_instance;
	}
private:
	//构造私有
	Singleton()
	{
		cout << "调用构造函数" << endl;
	}  
	//Singleton()=default;


	~Singleton()
	{
		cout << "调用析构函数" << endl;
	} 
	//~Singleton() = default;

	//禁用拷贝构造和赋值运算符(=delete 为C++11新标准)
	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

private:
	//静态私有对象
	static Singleton* m_instance;
};

Singleton* Singleton::m_instance = nullptr; //初始化
#include "singleton.h"

int main()
{
	Singleton* instance1 = Singleton::getInstance();
	Singleton* instance2 = Singleton::getInstance();
	return 0;

}

 执行结果:

由上述结果可知,的确只创建了一个实例。

但同时暴露了两个问题:①线程安全;②内存泄漏

①线程安全:在多线程场景下,可能多个线程进行new操作,需要加锁进行限制,保证只进行一次new操作。

#include "singleton.h"

int main()
{
	thread t1([] {Singleton* s1 = Singleton::getInstance();});
	thread t2([] {Singleton* s2 = Singleton::getInstance();});

	t1.join();
	t2.join();
	return 0;

}

 

 ②内存泄漏:new在堆上的资源在程序结束时,需要通过delete进行释放。上面并没有调用析构函数执行delete操作。

2.锁+静态成员析构单例

#include <iostream>
#include <mutex>
using namespace std;

//锁+静态成员析构单例
class Singleton
{
public:
	static Singleton* getInstance()
	{
		m_mutex.lock();//上锁
		if (m_instance == nullptr)
		{
			cout << "创建实例" << endl;
			m_instance = new Singleton;
		}
		m_mutex.unlock();//解锁
		return m_instance;
	}
private:
	Singleton()
	{
		cout << "调用构造函数" << endl;
	}  
	//Singleton()=default;

	~Singleton()
	{
		cout << "调用析构函数" << endl;
	} 
	//~Singleton() = default;

	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

private:
	class FreeInstace
	{
	public:
		FreeInstace()=default;
		~FreeInstace()
		{
			if (Singleton::m_instance != nullptr)
			{
				delete Singleton::m_instance;
				cout << "单例销毁" << endl;

			}
		}
	};
private:
	//静态私有对象
	static Singleton* m_instance;
	static FreeInstace m_freeinstance;
	static mutex m_mutex;
};

Singleton* Singleton::m_instance = nullptr; //初始化
Singleton::FreeInstace Singleton::m_freeinstance;
mutex Singleton::m_mutex;

该方案的缺点在于对Singleton的每次访问都需要获取一个锁,锁导致速度慢,效率低。但实际上,我们只需要一个锁,初始化m_instance时(即确定m_instance指向时),这应该只在第一次调用实例时发生。如果在程序运行的过程中调用了n次instance,则只在第一次调用时需要锁。当你知道n - 1个锁是不必要的,为什么还要为n个锁的获取买单呢?

3.双层检查锁定优化

static Singleton* getInstance()
	{
		if (m_instance == nullptr)
		{
			m_mutex.lock();//上锁
			if (m_instance == nullptr)
			{
				cout << "创建实例" << endl;
				m_instance = new Singleton;
			}
			m_mutex.unlock();//解锁
		}
		return m_instance;
	}

双层检查锁定的关键是观察到大多数对instance的调用将看到m_instance是非空的,因此不会尝试初始化它。因此,它尝试获取锁之前测试m_instance是否为空。只有当测试成功(即m_instance尚未初始化)时,才会获得锁,然后再次执行测试以确保m_instance仍然为空(因此称为双重检查锁定)。第二个测试是必要的,因为,正如上面描述的情况在m_instance第一次被测试到获得锁的时间之间,有可能发生另一个线程初始化m_instance的情况

使用双层检查锁定将已经初始化的对象的直接返回。可以使代码性能会大大加快。但它们没有考虑到一个更基本的问题,即确保在双层检查锁定期间执行的机器指令以可接受的顺序执行。

m_instance = new Singleton;

这个语句导致三件事发生:
步骤1:分配内存来保存Singleton对象。
步骤2:在分配的内存中构造一个单例对象。
步骤3:使m_instance 指向已分配的内存。
最重要的是观察到编译器不受约束,会按照这个顺序执行这些步骤!

特别是,编译器有时允许交换步骤2和步骤3。所以可能导致访问到未初始化的对象的引用。

解决方案:可以参考如下链接C++完美单例模式 - 简书

4.双层检查锁定+智能指针

针对内存泄漏问题,除了可以方法2介绍的使用静态成员在程序结束时,销毁成员是调用析构进行delete,还可以使用智能指针,头文件引用<memory>

class Singleton
{
public:
	static shared_ptr<Singleton> getInstance()
	{
		if (m_instance == nullptr)
		{
			m_mutex.lock();//上锁
			if (m_instance == nullptr)
			{
				cout << "创建实例" << endl;
				m_instance.reset( new Singleton(), destoryInstance);
			}
			m_mutex.unlock();//解锁
		}
		return m_instance;
	}
	static void destoryInstance(Singleton* x) 
	{
		cout << "自定义释放实例" << endl;
		delete x;
	}
private:
	Singleton()
	{
		cout << "调用构造函数" << endl;
	}
	//Singleton()=default;

	~Singleton()
	{
		cout << "调用析构函数" << endl;
	}
	//~Singleton() = default;

	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

private:
	//静态私有对象
	static  shared_ptr<Singleton> m_instance;
	static mutex m_mutex;
};

shared_ptr<Singleton> Singleton::m_instance = nullptr; //初始化
mutex Singleton::m_mutex;

应用智能指针后,在程序结束时,它自动进行资源的释放,解决了内存泄漏的问题。


三、饿汉模式实现

饿汉和懒汉的差别就在于,饿汉提前进行了创建。

1.基础实现

class Singleton
{
public:
	//公共接口获取唯一实例
	static Singleton* getInstance()
	{
		return m_instance;
	}
private:
	//构造私有
	Singleton()
	{
		cout << "调用构造函数" << endl;
	}  
	//Singleton()=default;


	~Singleton()
	{
		cout << "调用析构函数" << endl;
	} 
	//~Singleton() = default;

	//禁用拷贝构造和赋值运算符(=delete 为C++11新标准)
	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

private:
	//静态私有对象
	static Singleton* m_instance;
};

Singleton* Singleton::m_instance = new Singleton; //初始化

所以main还没开始,实例就已经构建完毕。获取实例的函数也不需要进行判空操作,因此也就不用双重检测锁来保证线程安全了,它本身已经是线程安全状态了。

但是内存泄漏的问题还是要解决的。

2.嵌套内部类解决内存泄漏

class Singleton
{
public:
	//公共接口获取唯一实例
	static Singleton* getInstance()
	{
		return m_instance;
	}
private:
	//构造私有
	Singleton()
	{
		cout << "调用构造函数" << endl;
	}
	//Singleton()=default;


	~Singleton()
	{
		cout << "调用析构函数" << endl;
	}
	//~Singleton() = default;

	//禁用拷贝构造和赋值运算符(=delete 为C++11新标准)
	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

private:
	class FreeInstace
	{
	public:
		FreeInstace()=default;
		~FreeInstace()
		{
			if (Singleton::m_instance != nullptr)
			{
				delete Singleton::m_instance;
				cout << "单例销毁" << endl;

			}
		}
	};
private:
	//静态私有对象
	static Singleton* m_instance;
	static FreeInstace m_freeinstance;
};

Singleton* Singleton::m_instance = new Singleton; //初始化
Singleton::FreeInstace Singleton::m_freeinstance;

3.智能指针解决内存泄漏 

class Singleton
{
public:
	//公共接口获取唯一实例
	static shared_ptr<Singleton> getInstance()
	{
		return m_instance;
	}

	static void destoryInstance(Singleton* x) {
		cout << "自定义释放实例" << endl;
		delete x;
	}
private:
	//构造私有
	Singleton()
	{
		cout << "调用构造函数" << endl;
	}  
	//Singleton()=default;


	~Singleton()
	{
		cout << "调用析构函数" << endl;
	} 
	//~Singleton() = default;

	//禁用拷贝构造和赋值运算符(=delete 为C++11新标准)
	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

private:
	//静态私有对象
	static shared_ptr<Singleton> m_instance;
};

shared_ptr<Singleton>  Singleton::m_instance ( new Singleton, destoryInstance); //初始化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/41503.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

linux 系统修改已经打好jar包的yml配置文件

工作中可能回遇到&#xff0c;jar包已经打好&#xff0c;并且文件已经上传了&#xff0c;但是突然发现配置文件中的某一个参数写错了&#xff0c;怎么办&#xff1f;重新打包&#xff1f;如果重新打包再上传的话太影响效率了。那么我们可以通过以下方法&#xff0c;修改已经上传…

SuperMap iServer新增支持FlatGeobuf数据格式,查询渲染性能提升2-3倍

导语 FlatGeobuf是一种地理数据存储格式&#xff0c;采用了二进制编码&#xff0c;相比其他文本或XML格式更高效&#xff0c;可以显著减小文件大小&#xff0c;这使得数据的传输和存储更加快速和高效。 SuperMap iServer 11i(2023) &#xff08;以下简称SuperMap iServer11.1&a…

Pandas Groupby:在Python中汇总、聚合和分组数据

GroupBy是一个非常简单的概念。我们可以创建一个类别分组&#xff0c;并对这些类别应用一个函数。这是一个简单的概念&#xff0c;但它是一种在数据科学中广泛使用的非常有价值的技术。在真实的的数据科学项目中&#xff0c;您将处理大量数据并一遍又一遍地尝试&#xff0c;因此…

elementUI el-radio 无法点击的问题

<el-form-item label"B端客户类型" prop"user_type"><template slot"label"><span>B端客户类型</span><el-tooltip effect"dark" placement"top" content"B端大客户账期有效,只有设置该类型…

【Go】实现一个代理Kerberos环境部分组件控制台的Web服务

实现一个代理Kerberos环境部分组件控制台的Web服务 背景安全措施引入的问题SSO单点登录 过程整体设计路由反向代理登录会话组件代理YarnHbase 结果 背景 首先要说明下我们目前有部分集群的环境使用的是HDP-3.1.5.0的大数据集群&#xff0c;除了集成了一些自定义的服务以外&…

寻找下一个生成式 AI 独角兽,亚马逊云科技创业加速器火热招募中!

生成式AI让人工智能技术又一次破圈&#xff0c;带来了机器学习被大规模采用的历史转折点。它正在掀起新一轮的科技革命&#xff0c;为人类带来前所未有的颠覆性的影响&#xff0c;而诸多创业者也应势而上&#xff0c;寻求创新机遇。生成式AI可以创造全新的客户体验、提高企业内…

fastapi初使用,构建自己的api

文章目录 1、安装2、api实现2.1、 app.get("/1")2.2、app.get("/{a}")2.3、app.get("/{a}{b}")2.4、函数和api分离 3、运行 原文链接&#xff1a;https://wangguo.site/posts/d98bb3c9.html fastapi 是一个基于 Python 的 API 构建框架&#xff…

044、TiDB特性_PlacementPolicy

Placement Rules in SQL之前 跨地域部署的集群&#xff0c;无法本地访问无法根据业务隔离资源难以按照业务登记配置资源和副本数 Placement Rules in SQL之后 跨地域部署的集群&#xff0c;支持本地访问根据业务隔离资源按照业务等级配置资源和副本数 配置 labels 设置 Ti…

Windows Cluster 分布式算法

在分布式系统中&#xff0c;都需要解决分布式一致性问题。那么&#xff0c;在Windows 集群中&#xff0c;使用了什么算法来保证集群的一致性呢——Paxos。Windows Server 故障转移集群 (WSFC) 使用 Paxos 算法在整个系统中同步更改。通过记录 Paxos Tag 值并保留历史记录&#…

vue 如何发布并部署到服务器

一般情况npm run build即可 从而生成vue代码直接放到服务器即可 这里的具体情况要看package.json里面的配置从而使用命令 会生成dist就是该项目的发布包

Inno Setup打包winform、wpf程序可判断VC++和.net环境

Inno Setup打包winform、wpf程序可判断VC和.net环境 1、下载Inno Setup2、新建打包文件、开始打包1、新建打包文件2、填写 应用名称、版本号、公司名称、公司官网3、选择安装路径 Custom是指定默认路径、Program Files folder是默认C盘根目录4、选择程序启动exe文件 以及Addfol…

Ubuntu下安装、配置及重装CUDA教程

安装CUDA 前往Nvidia CUDA Tools官网选择对应的架构和版本下载CUDA 以如下架构和版本为例&#xff1a; 查看显卡驱动 nvidia-smi如果显卡驱动已经装了&#xff0c;那么在CUDA安装过程中不用再勾选安装driver 下载并安装CUDA wget https://developer.download.nvidia.co…

ETHERCAT转ETHERCAT网关西门子为什么不支持ethercat两个ETHERCAT设备互联

1.1 产品功能 远创智控YC-ECT-ECT是自主研发的一款ETHERCAT从站功能的通讯网关。该产品主要功能是将2个ETHERCAT网络连接起来。 本网关连接到ETHERCAT总线中做为从站使用。 1.2 技术参数 1.2.1 远创智控YC-ECT-ECT技术参数 ● 网关做为ETHERCAT网络的从站&#xff0c;可以连接…

HTML5学习简记

目录 HTML定义 标签 HTML基本骨架 常见标签 标题标签 段落标签 换行与水平线标签 文本格式化标签 图像标签 绝对路径与相对路径 超链接标签 音频与视频标签 列表标签 无序列表 有序列表 定义列表 表格标签 表格结构标签 合并单元格 表单标签 input标签 input标签占…

CSS---CSS面试题

目录 1.盒模型 2.offsetHeight /clientheight/scrollHeight 3.left与offsetLeft 4.对BFC规范的理解 5.解决元素浮动导致的父元素高度塌陷的问题 6.CSS样式的先级 7.隐藏页面元素 8.display: none 与 visibility: hidden 的区别 9.页面引入样式时&#xff0c;使用link与import有…

GO 语言GC

目录 写屏障 读屏障 GO语言GC准备 堆内存结构: GC内存分配: GC触发&#xff1a; P的作用: 写屏障 实现强弱三色不式,为了避免误删,则实现写屏障. 写屏障是在写操作中插入指令,目的是把数据对象的修改通知到GC GO语言支持两种写屏障 读屏障 非移动垃圾回收(例如 三色)天…

文件共享服务器

文章目录 一、共享服务器概述二、创建共享三、访问共享四、创建隐藏的共享五、访问隐藏共享的方法六、共享相关命令七、屏蔽系统隐藏共享自动产生1. 打开注册表2. 定位共享注册表位置 八、查看本地网络连接状态&#xff08;查看开放端口&#xff09;九、关闭445服务 一、共享服…

java线上故障排查套路总结

线上故障主要会包括cpu、磁盘、内存以及网络问题&#xff0c;而大多数故障可能会包含不止一个层面的问题&#xff0c;所以进行排查时候尽量四个方面依次排查一遍。同时例如jstack、jmap等工具也是不囿于一个方面的问题的&#xff0c;基本上出问题就是df、free、top 三连&#x…

【Vue/element】 el-table实现表格动态新增/插入/删除 表格行,可编辑单元格

el-table实现表格动态新增/插入/删除 表格行&#xff0c;可编辑单元格 效果如下&#xff1a; 点击“新增一行”可以在表格最后新增一行&#xff0c;单元格内容可编辑 点击绿色按钮&#xff0c;可在指定行的后面插入一行 点击红色-按钮&#xff0c;可以删除指定行 原理&#…

【IM群发苹果日历】获取推送通知的设备标识符(Device Token)

苹果日历群发部署设置推送服务器推送服务器&#xff0c;用于向苹果日历应用的设备发送推送通知。推送服务器需要能够与Apple Push Notification Service (APNs) 进行通信。怎样来做&#xff1f; 部署设置推送服务器并与APNs进行通信需要以下步骤&#xff1a; 获取APNs证书&am…