【C++】单例模式【两种实现方式】

目录
一、了解单例模式前的基础题
1、设计一个类,不能被拷贝
2、设计一个类,只能在堆上创建对象
3、设计一个类,只能在栈上创建对象
4、设计一个类,不能被继承
二、单例模式
1、单例模式的概念
2、单例模式的两种实现方式
2.1 懒汉模式实现单例模式
2.2 饿汉模式实现单例模式
一、了解单例模式前的基础题
1、设计一个类,不能被拷贝
拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载,因此 想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可 。
将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。class CopyBan {// ...private:CopyBan(const CopyBan&);CopyBan& operator=(const CopyBan&);//... };原因:1. 设置成私有:如果只声明没有设置成 private ,用户自己如果在类外定义了,就可以不能禁止拷贝了2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
C++11 扩展 delete 的用法, delete 除了释放 new 申请的资源外,如果在默认成员函数后跟上=delete ,表示让编译器删除掉该默认成员函数。class CopyBan {// ...CopyBan(const CopyBan&)=delete;CopyBan& operator=(const CopyBan&)=delete;//... };
2、设计一个类,只能在堆上创建对象
思路:
创建对象一定调用构造函数(或拷贝构造:两种禁用方式),故先禁掉构造函数(私有化构造函数,对于拷贝构造下面有两种方式),则放在类中的私有下,但是怎么在堆上创建对象?用一个成员函数GetObj来在堆上创建对象(因为类内能访问私有成员中的构造函数,类外不可以),那为什么要用static修饰GetObj?这样就可以用类名::GetObj来访问了,而不用创建一个对象才能访问GetObj(因为你调用GetObj之前创建的对象一定是在栈上的),此外拷贝构造要设置为私有或直接=delete,即不能使用,因为存在你拷贝构造一个栈上的对象的场景(即用拷贝构造来创建对象)
1. 将类的构造函数私有,拷贝构造声明为delete,防止别人调用拷贝在栈上生成对象。2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
class HeapOnly
{
public:static HeapOnly* GetObj(){//专门用来在堆上创建对象return new HeapOnly;//注意这里是返回HeapOnly指针,那只是指针的拷贝,//而不是对象的拷贝,故不会调用构造函数}//C++11:防拷贝:拷贝构造函数声明成deleteHeapOnly(const HeapOnly&) = delete;
private://构造函数私有化HeapOnly(){}//C++98防拷贝:拷贝构造函数声明为私有//HeapOnly(const HeapOnly&);
};int main()
{//HeapOnly hp; //在栈上创建,失败HeapOnly* p = HeapOnly::GetObj();//成功【创建一个在堆上的对象】std::shared_ptr<HeapOnly> sp1(HeapOnly::GetObj());std::shared_ptr<HeapOnly> sp2(HeapOnly::GetObj());//HeapOnly copy(*sp1);//用栈上的对象来拷贝构造copy是不行的,故要禁掉拷贝构造return 0;
} 3、设计一个类,只能在栈上创建对象
和只能在堆上创建对象思路的唯一区别在于:创建的栈的对象需传值返回,对象的拷贝要调用拷贝构造函数,所以不能禁掉拷贝构造函数
class StackOnly
{
public:static StackOnly CreateObj(){//因为返回个匿名对象,传值返回,会调用拷贝构造//故不能禁掉拷贝构造return StackOnly();}private:StackOnly(){}
};int main()
{StackOnly obj = StackOnly::CreateObj();//StackOnly* ptr3 = new StackOnly; //失败
} 下面是有缺陷的代码:
下面的代码只是禁掉了在堆区创建数据,但是在静态区创建的数据还是无法阻止
//该方案存在一定程度缺陷,无法阻止在数据段(静态区)创建对象
class StackOnly
{
public:// 禁掉operator new可以把下面用new 调用拷贝构造申请对象给禁掉void* operator new(size_t size) = delete;
};int main()
{StackOnly so;//new分为operator new + 构造函数//StackOnly* ptr3 = new StackOnly(obj); //失败static StackOnly sso;//在静态区上开辟成功return 0;
} 4、设计一个类,不能被继承
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承 class NonInherit { public:static NonInherit GetInstance(){return NonInherit();} private:NonInherit(){} };
final 关键字, final 修饰类,表示该类不能被继承。class A final {// .... };
二、单例模式
1、单例模式的概念
设计模式:设计模式( Design Pattern )是一套 被反复使用、多数人知晓的、经过分类的、代码设计经验的 总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有 套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。单例模式:一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。![]()
单例模式有两种实现模式:懒汉模式和饿汉模式
2、单例模式的两种实现方式
设计一个类,只能创建一个对象(单例模式)
那么题目的意思就是保证全局只有一个实例对象
①、单例模式大体结构(有缺陷)
class Singleton
{
public:static Singleton* GetInstance(){if (_pinst == nullptr){//因为是静态成员变量,除了第一次为nullptr//再进来不是nullptr了,直接返回_pinst即可_pinst = new Singleton;}return _pinst;}private:Singleton(){}Singleton(const Singleton& s) = delete;static Singleton* _pinst;//静态成员的声明
};Singleton* Singleton::_pinst = nullptr;//静态成员的定义int main()
{//Singleton s1; //失败//保证获取的对象每次都是同一个cout << Singleton::GetInstance() << endl;cout << Singleton::GetInstance() << endl;cout << Singleton::GetInstance() << endl;//Singleton copy(*Singleton::GetInstance());//无法调用拷贝构造return 0;
}
运行结果:

上面代码缺陷是线程安全问题:如果两个线程同时要new一个对象(即_pinst = new Singleton),这时就发生错误了,我们要的是只有一个对象,故引出锁解决
那我们先看看这种错误出现的场景:
为了防止线程跑太快而达不到我们想看到错误情况的效果,我们用sleep睡眠来辅助


为解决上面的问题,我们用锁
2.1 懒汉模式实现单例模式
①、错误代码1
//懒汉模式:第一次获取对象时,再创建对象
class Singleton
{
public:static Singleton* GetInstance(){_mtx.lock();if (_pinst == nullptr){//因为是静态成员变量,除了第一次为nullptr//再进来不是nullptr了,直接返回_pinst即可_pinst = new Singleton;}_mtx.unlock();return _pinst;}Singleton(const Singleton& s) = delete;private:Singleton(){}static Singleton* _pinst;//静态成员的声明static mutex _mtx;
};Singleton* Singleton::_pinst = nullptr;//静态成员的定义
mutex Singleton::_mtx;
上面代码意义是创建对象保证只有一个线程在访问,解决了不会同时创建对象的问题,但是如果new失败了要抛异常怎么办?此时正在访问的线程都没有解锁,其他线程也无法访问了,故要用unique_lock:也会帮你锁,且不管你是否主动unlock解锁,都会在出了作用域后解锁
②、用unique_lock来改进

③、只需第一次加锁
只要_pinst指向已经new出来的实例对象,就无须加锁了

④、析构单例模式的对象
一般单例模式下的new出来的这个全局唯一对象是不需要释放的,因为这种单例模式下的对象,整个程序只有一个,它是一直在用的,没必要释放。
如果你就想要释放的话,两种方式:
①、静态函数

②、静态变量的生命周期

#include<vector>
#include<thread>
#include<mutex>namespace lazy_man
{//懒汉模式:第一次获取对象时,再创建对象class Singleton{public:static Singleton* GetInstance(){//_mtx.lock();unique_lock会锁,锁完之后不管你是否解锁,出了作用域他都会自动解锁而你现在就这一个地方需要锁,故再加个{}作用域//{// unique_lock<mutex> lock(_mtx);// if (_pinst == nullptr)// {//因为是静态成员变量,除了第一次为nullptr// //再进来不是nullptr了,直接返回_pinst即可// _pinst = new Singleton;// }//}//双检查:if (_pinst == nullptr){//加锁只是为了保护第一次{unique_lock<mutex> lock(_mtx);if (_pinst == nullptr){//因为是静态成员变量,除了第一次为nullptr//再进来不是nullptr了,直接返回_pinst即可_pinst = new Singleton;//只要_pinst指向已经new出来的实例对象,就无须加锁了}}}//_mtx.unlock();return _pinst;}//如果你就想释放这个对象的话,自己写个静态函数即可,手动调static void DelInstance(){unique_lock<mutex> lock(_mtx);delete _pinst;_pinst = nullptr;}Singleton(const Singleton& s) = delete;private:Singleton(){}static Singleton* _pinst;//静态成员的声明static mutex _mtx;};Singleton* Singleton::_pinst = nullptr;//静态成员的定义mutex Singleton::_mtx;//1、如果要手动释放单例对象,可以调用DelInstance//2、如果需要程序结束时,正常释放单例对象,可以加入下面的设计class GC{public:~GC(){Singleton::DelInstance();}};static GC gc;//main函数结束就会调用它的析构函数,进而释放_pinstvoid x(){Singleton s1; //失败保证获取的对象每次都是同一个//cout << Singleton::GetInstance() << endl;//cout << Singleton::GetInstance() << endl;//cout << Singleton::GetInstance() << endl;Singleton copy(*Singleton::GetInstance());//无法调用拷贝构造//代码中存在线程问题:若多个线程同时获取一个对象呢?vector<std::thread> vthreads;int n = 4;for (size_t i = 0; i < n; ++i){vthreads.push_back(std::thread([](){//cout << std::this_thread::get_id() << ":";cout << Singleton::GetInstance() << endl;}));//线程对象里面用了一个lambda表达式}for (auto& t : vthreads){t.join();}}
}int main()
{lazy_man::x();return 0;
}
运行结果:

2.2 饿汉模式实现单例模式
饿汉模式有个静态成员变量,静态变量在程序运行前创建,在程序的整个运行期间始终存在,他始终保持原先的值,除非给他赋予一个不同的值或程序结束。正因为程序前创建,那此时只有主线程,不存在线程安全问题。
namespace hungry_man
{//饿汉模式 --main函数之前就创建对象class Singleton{public:static Singleton* GetInstance(){return &_inst;}Singleton(const Singleton& s) = delete;private: Singleton(){}static Singleton _inst;};//static对象是在main函数之前创建的,这时只有主线程,故不存在线程安全问题Singleton Singleton::_inst; void x(){Singleton s1; //失败保证获取的对象每次都是同一个//cout << Singleton::GetInstance() << endl;//cout << Singleton::GetInstance() << endl;//cout << Singleton::GetInstance() << endl;Singleton copy(*Singleton::GetInstance());//无法调用拷贝构造//代码中存在线程问题:若多个线程同时获取一个对象呢?vector<std::thread> vthreads;int n = 4;for (size_t i = 0; i < n; ++i){vthreads.push_back(std::thread([](){//cout << std::this_thread::get_id() << ":";cout << Singleton::GetInstance() << endl;}));//线程对象里面用了一个lambda表达式}for (auto& t : vthreads){t.join();}}
}int main()
{hungry_man::x();return 0;
}

2.3懒汉和饿汉模式的区别

相关文章:
【C++】单例模式【两种实现方式】
目录 一、了解单例模式前的基础题 1、设计一个类,不能被拷贝 2、设计一个类,只能在堆上创建对象 3、设计一个类,只能在栈上创建对象 4、设计一个类,不能被继承 二、单例模式 1、单例模式的概念 2、单例模式的两种实现方式 …...
php的api接口token简单实现
<?php // 生成 Token function generateToken() {$token bin2hex(random_bytes(16)); // 使用随机字节生成 tokenreturn $token; } // 存储 Token(这里使用一个全局变量来模拟存储) $tokens []; // 验证 Token function validateToken($token) {gl…...
CCNA课程实验-13-PPPoE
目录 实验条件网络拓朴需求 配置实现基础配置模拟运营商ISP配置ISP的DNS配置出口路由器OR基础配置PC1基础配置 出口路由器OR配置PPPOE拨号创建NAT(PAT端口复用) PC1测试结果 实验条件 网络拓朴 需求 OR使用PPPoE的方式向ISP发送拨号的用户名和密码,用户名…...
cocosCreator 之 Bundle使用
版本: v3.4.0 语言: TypeScript 环境: Mac Bundle简介 全名 Asset Bundle(简称AB包),自cocosCreator v2.4开始支持,用于作为资源模块化工具。 允许开发者根据项目需求将贴图、脚本、场景等资源划分在 Bundle 中&am…...
分类网络搭建示例
搭建CNN网络 本章我们来学习一下如何搭建网络,初始化方法,模型的保存,预训练模型的加载方法。本专栏需要搭建的是对分类性能的测试,所以这里我们只以VGG为例。 请注意,这里定义的只是一个简陋的版本,后续一…...
为 Ubuntu 虚拟机构建 SSH 服务器
以校园网环境和VMware为例,关键步骤如下: 安装 SSH 服务: 打开 Ubuntu 虚拟机。打开终端。输入命令 sudo apt-get update 更新软件包列表。输入命令 sudo apt-get install openssh-server 安装 SSH 服务。 配置 SSH 服务: 编辑配…...
SpringBoot--中间件技术-2:整合redis,redis实战小案例,springboot cache,cache简化redis的实现,含代码
SpringBoot整合Redis 实现步骤 导pom文件坐标 <!--redis依赖--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency>yaml主配置文件,配置…...
linux rsyslog配置文件详解
1.rsyslog配置文件简介 linux rsyslog配置文件/etc/rsyslog.conf分为三部分:MODULES、GLOBAL DIRECTIVES、RULES ryslog模块说明 模块说明MODULES指定接收日志的协议和端口。若要配置日志服务器,则需要将相应的配置项注释去掉。GLOBAL DIRECTIVES主要用来配置日志模版。指定…...
wordpress是什么?快速搭网站经验分享
作者主页 📚lovewold少个r博客主页 ⚠️本文重点:c入门第一个程序和基本知识讲解 👉【C-C入门系列专栏】:博客文章专栏传送门 😄每日一言:宁静是一片强大而治愈的神奇海洋! 目录 前言 wordp…...
排序 算法(第4版)
本博客参考算法(第4版):算法(第4版) - LeetBook - 力扣(LeetCode)全球极客挚爱的技术成长平台 本文用Java实现相关算法。 我们关注的主要对象是重新排列数组元素的算法,其中每个元素…...
asp.net 在线音乐网站系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio
一、源码特点 asp.net 在线音乐网站系统是一套完善的web设计管理系统,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为vs2010,数据库为sqlserver2008,使用c#语言 开发 asp.net 在线音乐网站系统1 应用…...
ElastaticSearch -- es之Filters aggregation 先过滤再聚合
使用场景 使用es时,有时我们需要先过滤后再聚合,但如果直接在query的filter中过滤,不止会影响到一个聚合,还会影响到其他的聚合结果。 比如,我们想要统计深圳市某个品牌的总销售额,以及该品牌的女款衣服的…...
如何把一个接口设计好?
如何把一个接口设计好? 如何设计一个接口?是在我们日常开发或者面试时经常问及的一个话题。很多人觉得这不就是CRUD,能实现不就行了。单纯实现来说,并非难事,但要做到易用、易扩展、易维护并不是一件简单的事。这里并…...
mini-vue 的设计
mini-vue 的设计 mini-vue 使用流程与结果预览: <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name&qu…...
React整理杂记(一)
1.React三项依赖 1.react.js -> 核心代码 2.react-dom.js -> 渲染成dom 3.babel.js->非必须,将jsx转为js 类组件中直接定义的方法,都属于严格模式下 this的绑定可以放到constructor(){}中 2. JSX语法 1.可以直接插入的元素: num…...
[100天算法】-统计封闭岛屿的数目(day 74)
题目描述 有一个二维矩阵 grid ,每个位置要么是陆地(记号为 0 )要么是水域(记号为 1 )。我们从一块陆地出发,每次可以往上下左右 4 个方向相邻区域走,能走到的所有陆地区域,我们将其…...
esp32-rust-std-examples-blinky
以下为在 ESP-IDF (FreeRTOS) 上运行的 blinky 示例: https://github.com/esp-rs/esp-idf-hal/blob/master/examples/blinky.rs //! Blinks an LED //! //! This assumes that a LED is connected to GPIO4. //! Depending on your target and the board you are …...
【docker容器技术与K8s】
【docker容器技术与K8s】 一、Docker容器技术 1、Docker的学习路线 (1)学习Docker基本命令(容器管理和镜像管理) (2)学习使用Docker搭建常用软件 (3)学习Docker网络模式 启动容器的…...
RT-DTER 引入用于低分辨率图像和小物体的新 CNN 模块 SPD-Conv
论文地址:https://arxiv.org/pdf/2208.03641v1.pdf 代码地址:https://github.com/labsaint/spd-conv 卷积神经网络(CNN)在图像分类、目标检测等计算机视觉任务中取得了巨大的成功。然而,在图像分辨率较低或对象较小的更困难的任务中,它们的性能会迅速下降。 这源于现有CNN…...
Folw + Room 实现自动观察数据库的刷新
1、Room :定义数据结构、创建数据库 // 定义实体 Entity data class TestModel ()// 定义数据库 Dao interface TestDao { Query("SELECT * FROM TestTable") fun getAll(): List<TestModel> }// 获取数据库 abstract class TestDatabase: RoomDat…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...
ubuntu22.04有线网络无法连接,图标也没了
今天突然无法有线网络无法连接任何设备,并且图标都没了 错误案例 往上一顿搜索,试了很多博客都不行,比如 Ubuntu22.04右上角网络图标消失 最后解决的办法 下载网卡驱动,重新安装 操作步骤 查看自己网卡的型号 lspci | gre…...
