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

 

目录

 一、了解单例模式前的基础题

1、设计一个类,不能被拷贝

2、设计一个类,只能在堆上创建对象

3、设计一个类,只能在栈上创建对象

4、设计一个类,不能被继承

二、单例模式

1、单例模式的概念

2、单例模式的两种实现方式 

2.1 懒汉模式实现单例模式

2.2 饿汉模式实现单例模式


 一、了解单例模式前的基础题

1、设计一个类,不能被拷贝

拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载,因此 想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可
C++98
将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
class CopyBan
{// ...private:CopyBan(const CopyBan&);CopyBan& operator=(const CopyBan&);//...
};
原因:
1. 设置成私有:如果只声明没有设置成 private ,用户自己如果在类外定义了,就可以不
能禁止拷贝了
2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写
反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
C++11
 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方式
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit(){}
};
C++11方式
 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懒汉和饿汉模式的区别


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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt工程打包工具 windeployqt 的用法

1.复制工程下的“Debug”或者“Release”文件夹到你喜欢的路径&#xff0c;例如&#xff1a;D:\QT_out\ 2.在操作系统“开始”选项找到“Qt”文件夹&#xff0c;打开“Qt 5.15.2&#xff08;MSVC 2019 64-bit&#xff09;” 重点&#xff1a; 这里要注意的是&#xff0c;一定…

Linux常见指令:从基础到理论

前言 目录 前言 1. find指令 拓展 2. grep指令 拓展 sort指令 uniq指令 wc指令 3. zip/unzip指令 4. tar指令 5. uname指令 拓展 6. Linux常用热键 7. 关机 8. rz指令 拓展 scp指令 9. shell命令以及运行原理 Linux常见指令是使用Linux系统时必不可少的一部分。通过掌握…

简单好看个人引导页毛玻璃页面 HTML 源码

毛玻璃个人引导页源码&#xff0c;界面简洁&#xff0c;已测可完美搭建&#xff0c;UI非常不错的&#xff0c;有兴趣的自行去安装体验吧&#xff0c;其它就没什么好介绍的了。 学习资料源代码&#xff1a;百度网盘 请输入提取码&#xff1a;ig8c

[RCTF 2019]nextphp

文章目录 考点前置知识PHP RFC&#xff1a;预加载FFI基本用法PHP RFC&#xff1a;新的自定义对象序列化机制 解题过程 考点 PHP伪协议、反序列化、FFI 前置知识 PHP RFC&#xff1a;预加载 官方文档 通过查看该文档&#xff0c;在最下面找到预加载结合FFI的危害 FFI基本用法 …

Selenium关于内容信息的获取读取

在进行自然语言处理、文本分类聚类、推荐系统、舆情分析等研究中,通常需要使用新浪微博的数据作为语料,这篇文章主要介绍如果使用Python和Selenium爬取自定义新浪微博语料。因为网上完整的语料比较少,而使用Selenium方法有点简单、速度也比较慢,但方法可行,同时能够输入验…

yolov5 通过视频进行目标检测

打开yolov5-master文件夹&#xff0c;可以看到一个名为data的文件夹&#xff0c;在data中创建一个新的文件夹&#xff0c;命名为videos。 打开yolov5-master中的detect.py可以看到一行代码&#xff08;大概在245行左右&#xff09;为 parser.add_argument(--source, typestr,…

fastspar微生物相关性推断

fastspar 简介 fastspar是基于Sparcc通过C编写的&#xff0c;速度更快&#xff0c;内存消耗更少。sparcc是基于OTU的原始count数&#xff0c;通过log转换和标准化去除传统相对丰度的天然负相关&#xff08;因为所有OTU之和为1&#xff0c;某些OTU丰度高另外一些自然就少&…

tqdm学习

from tqdm import tqdmepochs 10 epoch_bar tqdm(range(epochs)) count 0 for _ in epoch_bar:count count1print("count {}".format(count))print(_)每次就是一个epoch

【Python】数据分析案例:世界杯数据可视化

文章目录 前期数据准备导入数据 分析&#xff1a;世界杯中各队赢得的比赛数分析&#xff1a;先打或后打的比赛获胜次数分析&#xff1a;世界杯中的抛硬币决策分析&#xff1a;2022年T20世界杯的最高得分者分析&#xff1a;世界杯比赛最佳球员奖分析&#xff1a;最适合先击球或追…

JAVA代码视频转GIF(亲测有效)

1.说明 本次使用的是JAVA代码视频转GIF&#xff0c;maven如下&#xff1a; <dependency><groupId>ws.schild</groupId><artifactId>jave-nativebin-win64</artifactId><version>3.2.0</version></dependency><dependency&…

07、SpringBoot+微信支付 -->处理超时订单(定时查询、核实微信支付平台的订单、调用微信支付平台查单接口、更新本地订单状态、记录支付日志)

目录 Native 支付处理超时订单定时的讲解需求分析代码定时任务&#xff1a;WxPayTask定时查询的方法&#xff1a;核实订单状态等操作 &#xff1a;WxPayServiceImpl查单接口方法&#xff1a;queryOrder更新本地订单状态&#xff1a;updateStatusByOrderNo记录支付日志&#xff…

苍穹外卖-day06

苍穹外卖-day06 课程内容 HttpClient微信小程序开发微信登录导入商品浏览功能代码 功能实现&#xff1a;微信登录、商品浏览 微信登录效果图&#xff1a; 商品浏览效果图&#xff1a; 1. HttpClient 1.1 介绍 HttpClient 是Apache Jakarta Common 下的子项目&#xff0c;…

单例模式 rust和java的实现

文章目录 单例模式介绍应用实例&#xff1a;优点使用场景 架构图JAVA 实现单例模式的几种实现方式 rust实现 rust代码仓库 单例模式 单例模式&#xff08;Singleton Pattern&#xff09;是最简单的设计模式之一。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建…

rabbitMQ rascal/amqplib报错 Error: Unexpected close 排查

以下是一些可能导致此 RabbitMQ 客户端或任何其他 RabbitMQ 客户端中的套接字读取或写入失败的常见场景 1.错过&#xff08;客户端&#xff09;心跳 第一个常见原因是RabbitMQ 检测到心跳丢失。发生这种情况时&#xff0c;RabbitMQ 将添加一个有关它的日志条目&#xff0c;然…

SQL note1:Basic Queries + Joins Subqueries

目录 一、Basic Queries 1、数据库术语 2、查表 3、过滤掉我们不感兴趣的行 4、布尔运算 5、过滤空值&#xff08;NULL&#xff09; 6、分组和聚合 1&#xff09;汇总数据的列 2&#xff09;汇总数据组 7、分组聚合的警告 1&#xff09;SELECT age, AVG(num_dogs) FR…

基于ssm的大学生社团管理系统

基于ssm的大学生社团管理系统 摘要 基于SSM的大学生社团管理系统是一个全面、高效的社团管理平台&#xff0c;旨在帮助大学生和社团管理员更方便、更快捷地进行社团活动的组织和管理。该系统基于Spring、SpringMVC和MyBatis&#xff08;简称SSM&#xff09;开发&#xff0c;这三…

Ubuntu中安装rabbitMQ

一、安装 RabbitMQ ①&#xff1a;更新源 sudo apt-get update②&#xff1a;安装Rrlang语言 由于RabbitMq需要erlang语言的支持&#xff0c;在安装RabbitMq之前需要安装erlang sudo apt-get install erlang-nox③&#xff1a;安装rabbitMQ sudo apt-get install rabbitmq-s…

【算法与数据结构】216、LeetCode组合总和 III

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;本题可以直接利用77题的代码【算法与数据结构】77、LeetCode组合&#xff0c;稍作修改即可使用。   …

APISpace IP归属地查询接口案例代码

1.IP归属地查询API 1.1 API接口简介 IP归属地查询API&#xff1a;根据IP地址查询归属地信息&#xff0c;包含国家、省、市、区县和运营商等信息。APISpace 提供了IPv4 和 IPv6 的IP归属地查询接口&#xff0c;并且包含了各种归属地精度查询的接口。 1.2 IPv4 IPv4归属地查询…

亚马逊云科技海外服务器初体验

目录 前言亚马逊云科技海外服务器概述注册使用流程实例创建性能表现用户体验服务支持初体验总结 前言 随着云原生技术的飞速发展&#xff0c;越来越多的企业和开发者选择云服务器来作为自己的使用工具&#xff0c;云原生技术的发展也促进了云服务厂商的产品发展&#xff0c;所…