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

news2024/11/28 18:38:17

目录

一、单例模式

单例模式的三个要点

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

常用的两类单例模式

 二、懒汉模式实现

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); //初始化

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一种用于RBF神经网络的新型自适应内核研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

vuejs源码之虚拟dom中的vnode

在虚拟dom中&#xff0c;vnode是比较重要的。 什么是vnode 在vuejs中&#xff0c;有一个Vnode类 使用它可以实例不同类型的vnode实例&#xff0c;而不同类型的vnode实例各自表示不同类型的dom元素。 例如dom元素有文本节点&#xff0c;元素节点&#xff0c;注释节点等。 co…

Spring IoC及DI依赖注入

Spring 1.Spring的含义&#xff1a; Spring 可从狭义与广义两个角度看待 狭义的 Spring 是指 Spring 框架(Spring Fremework) 广义的 Spring 是指 Spring 生态体系 2.狭义的 Spring 框架 Spring 框架是企业开发复杂性的一站式解决方案 Spring 框架的核心是 IoC 容器和 AO…

数据库java中jdbcTemplate的事务问题

1.什么都不设置事务是默认提交的 两次获取的连接是不是一样的 参考文献(重磅): (542条消息) JdbcTemplate的事务控制_jdbctemplate transactionmanager_DayDayUp丶的博客-CSDN博客 PostMapping("/pinYin22")CrossOriginTransactionalpublic String pinYin22(HttpS…

【js实现语言国际化】使用json配置文件实现

需求&#xff1a;使用js让项目实现中文简体、繁体跟英文的切换&#xff0c;实现语言国际化 首先准备三种json配置文件&#xff1a; en.json {"textOne": "Today is Monday","textTwo": "Tomorrow is Tuesday","textThree"…

F#奇妙游(14):F#实现WPF的绑定

WPF中的绑定 绑定在UI开发中是一个非常重要的概念&#xff0c;它可以让我们的UI界面和数据模型之间建立起联系&#xff0c;当数据模型发生变化时&#xff0c;UI界面也会随之变化&#xff0c;反之亦然。这样的好处是显而易见的&#xff0c;我们不需要手动去更新UI界面&#xff…

金智教育IPO过会:计划募资约6亿元,郭超、史鸣杰为实控人

7月13日&#xff0c;深圳证券交易所披露的信息显示&#xff0c;江苏金智教育信息股份有限公司&#xff08;下称“金智教育”&#xff09;获得上市委会议通过。据贝多财经了解&#xff0c;金智教育于2022年6月30日递交上市申请材料&#xff0c;先后递交了6个版本的招股书&#x…

NDK OpenGL与OpenCV实现大眼萌特效

NDK​系列之OpenGL与OpenCV实现大眼萌特效&#xff0c;本节主要是在上一节OpenGL仿抖音极快极慢录制特效视频上增加大眼萌的特效。 OpenGL视频特效系列&#xff1a; NDK OpenGL渲染画面效果 NDK OpenGL离屏渲染与工程代码整合 NDK OpenGL仿抖音极快极慢录制特效视频 NDK O…

通讯录实现

普通版 需求 通讯录可以用来存储1000个人的信息&#xff0c;每个人的信息包括&#xff1a;姓名、性别、年龄、电话、住址 提供方法&#xff1a; 添加联系人信息删除指定联系人信息查找指定联系人信息修改指定联系人信息显示所有联系人信息清空所有联系人以名字排序所有联系…

【Linux后端服务器开发】UDP协议

目录 一、端口号 二、UDP报头格式 三、UDP的特点 四、UDP协议实现网络聊天群 一、端口号 端口号port标识了一个主机上进行通信的不同的应用程序。 0 ~ 1023&#xff1a;系统端口号&#xff0c;HTTP、FTP、SSH等这些广为使用的应用层协议&#xff0c;它们的端口号都是固定…

Windows软件开发常用技巧总结

本文总结了本人在日常工作学习中遇到的问题及其解决方法&#xff0c;没有固定的涉及领域 目的就是为了在下一次遇到类似问题的时候方便查找&#xff0c;从而快速解决问题 本文不定时更新~ 目录 Windows使用 如何实现桌面图标随意排列 文件资源管理器相关 显示隐藏文件 修改…

Linux--获取最近一次的进程退出码:echo $?

举例&#xff1a; #include <stdio.h> int main() { printf("hello world,pid: %d,ppid: %…

JavaFx 用户界面控件3——TableView

1.表格视图 TableView ableView是JavaFX提供的一个强大的控件&#xff0c;可以用于显示表格数据。它通过为TableView设定items属性&#xff08;存储行数据的ObservableList对象&#xff09;和列属性&#xff08;TableColumn对象&#xff09;来完成数据填充与展示。 以下是一个…

如何做一线leader

文章目录 道领导力五个层次关键&#xff1a;信任 处事原则 术避坑指南事急则乱员工沟通向上管理人才招聘人才培养裁人员工关怀 道 领导力 五个层次 职位 当面交代事情&#xff0c;观察眼神、语气。反复确认有没有问题&#xff0c;如果有可以及时讨论策略&#xff0c;准备资源…

IDEA连接达梦数据库

在 IntelliJ IDEA 中连接达梦数据库&#xff0c;可以按照以下步骤进行操作&#xff1a; 1. 打开 IntelliJ IDEA&#xff0c;进入项目。 2. 在顶部菜单栏选择 "View" -> "Tool Windows" -> "Database"&#xff0c;打开数据库工具窗口。 3.…

自旋锁的优势和特点

ucos为何没自旋锁&#xff1f; UC/OS是一个适用于嵌入式系统的实时操作系统&#xff0c;它的设计目标是提供一种轻量级的任务调度和同步机制。相比于一般的操作系统&#xff0c;UC/OS在实现上更加精简&#xff0c;因此并没有像Linux那样的完整的锁机制。 UC/OS提供了一些基本…

17 | 从后端到前端:微服务后,前端如何设计?

微服务架构通常采用前后端分离的设计方式。作为企业级的中台&#xff0c;在完成单体应用拆分和微服务建设后&#xff0c;前端项目团队会同时面对多个中台微服务项目团队&#xff0c;这时候的前端人员就犹如维修电工一样了。 面对如此多的微服务暴露出来的 API 服务&#xff0c…

ChatGPT与Claude对比分析

一 简介 1、ChatGPT: 访问地址&#xff1a;https://chat.openai.com/ 由OpenAI研发,2022年11月发布。基于 transformer 结构的大规模语言模型,包含1750亿参数。训练数据集主要是网页文本,聚焦于流畅的对话交互。对话风格友好,回复通顺灵活,富有创造性。存在一定的安全性问题,可…

山西电力市场日前价格预测【2023-07-17】

日前价格预测 预测明日&#xff08;2023-07-17&#xff09;山西电力市场全天平均日前电价为335.50元/MWh。其中&#xff0c;最高日前电价为377.51元/MWh&#xff0c;预计出现在06: 00。最低日前电价为271.94元/MWh&#xff0c;预计出现在13: 30。 价差方向预测 1&#xff1a;实…

力扣 45. 跳跃游戏 II

题目来源&#xff1a;https://leetcode.cn/problems/jump-game-ii/description/ C题解1&#xff1a;因为每一步都要能走到下一个更远的地方&#xff0c;就比如 [2,3,1,1,4]&#xff0c;第一步虽然可以到索引2的位置&#xff0c;但是到索引1的位置下一步可以走更远。所以需要记录…