【C++】特殊类设计 | 单例设计模式

目录

    • 前言
    • 一、设计一个类,不能被拷贝
    • 二、设计一个只能在堆上创建对象的类
    • 三、设计一个只能在栈上创建对象的类
    • 四、设计一个类,不能被继承
    • 五、设计一个类,只能创建一个对象(单例模式)
      • 5.1 饿汉模式
      • 5.2 懒汉模式
      • 5.3 饿汉模式VS懒汉模式


前言

本篇文章我们重点要讲解的内容为单例模式,单例模式有什么特点?有什么好处?下面就让我们一起进入学习吧!!

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

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。

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;//...
};

二、设计一个只能在堆上创建对象的类

实现方式:

  • 思路一:将类的构造函数私有和拷贝构造声明成私有,防止别人调用拷贝在栈上生成对象。提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。

  • 思路二:将类的析构函数私有,提供一个释放资源的接口即可。因为对象出了作用域,会调用对象的析构函数来完成资源的释放,而在栈区或者静态区上创建的对象无法调用析构函数,但是在堆上创建的对象提供了释放资源的接口来完成资源的释放。

思路一代码:

class HeapOnly
{
public:static HeapOnly* CreateObject(){return new HeapOnly;   }HeapOnly(const HeapOnly& hp) = delete;HeapOnly operator=(const HeapOnly& hp) = delete;private:HeapOnly(){}int _x = 0;
};int main()
{HeapOnly* hp = HeapOnly::CreateObject();delete hp;/*HeapOnly a1;static HeapOnly a2;HeapOnly copy(*hp);*/return 0;
}

思路二代码:

class HeapOnly
{
public:HeapOnly(){}static void Delete1(HeapOnly* p){delete p;}void Delete2(){delete this;}HeapOnly(const HeapOnly& hp) = delete;HeapOnly operator=(const HeapOnly& hp) = delete;private:~HeapOnly(){}int _x = 0;
};int main()
{HeapOnly* p = new HeapOnly;HeapOnly::Delete1(p);  // 静态成员函数HeapOnly* s = new HeapOnly;s->Delete2();   // 普通成员函数//HeapOnly copy(*s);  // //HeapOnly s;return 0;
}

三、设计一个只能在栈上创建对象的类

思路:与上述方法相同,我们将构造函数私有化,提供一个静态成员函数来返回匿名对象,但其实我们返回的不是匿名对象而是匿名对象的临时拷贝变量,因此我们需要提供拷贝构造,提供了拷贝构造那么就不能限制对象在栈区/静态区上进行拷贝构造了,不能在堆上创建对象我们可以通过删除operator new 函数来实现。

class StackOnly
{
public:static StackOnly CreateObject(){return StackOnly();}//	// 不能防拷贝//	StackOnly(const StackOnly& hp) = delete;//	StackOnly& operator=(const StackOnly& hp) = delete;// 限制在堆上创建对象void* operator new(size_t n) = delete;private:StackOnly(){}int _x = 0;
};int main()
{StackOnly s = StackOnly::CreateObject();static StackOnly copy(s);// StackOnly ps* = new StackOnly;   //static StackOnly ps = new StackOnly;return 0;
}

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

C++98 的做法是将构造函数私有化,派生类中调不到基类的构造函数,则无法继承。

class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit(){}
};

而 C++ 11 的做法是 final 关键字修饰类,表示该类不能被继承。

class A final
{// ....
};

五、设计一个类,只能创建一个对象(单例模式)

设计模式:设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

单例模式: 一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

单例模式有两种实现模式,饿汉模式和懒汉模式。

5.1 饿汉模式

不管你将来用不用,程序启动时就创建一个唯一的实例对象,在进入main函数之前就已经实例化出一个对象。

整体思路:

  1. 把数据放进一个类里面,将这个类设计成单例类
  2. 把构造函数私有化,防止其随意创建对象,提供一个静态成员函数来返回单例对象
  3. 根据性质设计为饿汉或懒汉模式

饿汉模式的简化代码如下:

class Singleton 
{
public:static Singleton* GetInstance(){return _ins;   // 每次返回的对象都是指向同一块空间的}// 插入数据需要保证线程安全问题void Add(const string& str){_mtx.lock();_v.emplace_back(str);_mtx.unlock();}void Print(){_mtx.lock();for (auto& e : _v){cout << e << endl;}cout << endl;_mtx.unlock();}private:Singleton()  // 构造函数私有化,不能随意创建对象{}mutex _mtx;vector<string> _v;static Singleton* _ins;
};Singleton* Singleton::_ins = new Singleton;    // 在未进入main函数之前就已经实例化出来了int main()
{srand(time(0));int n = 100;thread t1([n]() {for (size_t i = 0; i < n; i++){Singleton::GetInstance()->Add("t1线程,第" + to_string(i) + "个" + to_string(rand()));}});thread t2([n]() {for (size_t i = 0; i < n; i++){Singleton::GetInstance()->Add("t2线程,第" + to_string(i) + "个" + to_string(rand()));}});t1.join();t2.join();Singleton::GetInstance()->Print();return 0;
}

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

5.2 懒汉模式

如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。

懒汉模式的简化代码:

class Singleton
{
public:static Singleton* GetInstance(){// 这里的判断是为了提高效率,假设对象已经创建出来了,那么我们就直接返回原来的对象即可if (_ins == nullptr)  {_imtx.lock();// 这里的判断是为了保证线程安全,假设没有这个条件判断,当t1获得锁然后new一个对象后解锁,此时t1时间片到了,t2获得了锁,如果此时不加判断的话那么就会覆盖掉t1 new的对象if (_ins == nullptr) {_ins = new Singleton;}_imtx.unlock();}return _ins;}void Add(const string& str){_mtx.lock();_v.emplace_back(str);_mtx.unlock();}void Print(){_mtx.lock();for (auto& e : _v){cout << e << endl;}cout << endl;_mtx.unlock();}static void DelInstance(){_imtx.lock();if (_ins){delete _ins;_ins = nullptr;}_imtx.unlock();}// 单例对象回收 —— Singleton析构之前先析构成员变量GC的资源,GC析构又会调用DelInstance隐式回收单例对象class GC{public:~GC(){DelInstance();}};private:Singleton(){}mutex _mtx;vector<string> _v;static Singleton* _ins;static mutex _imtx;static GC _gc;
};mutex Singleton::_imtx;    // 非对象成员函数中需要添加同一把锁来保证线程安全
Singleton* Singleton::_ins = nullptr;
Singleton::GC Singleton::_gc;int main()
{srand(time(0));int n = 100;thread t1([n]() {for (size_t i = 0; i < n; i++){Singleton::GetInstance()->Add("t1线程,第" + to_string(i) + "个" + to_string(rand()));}});thread t2([n]() {for (size_t i = 0; i < n; i++){Singleton::GetInstance()->Add("t2线程,第" + to_string(i) + "个" + to_string(rand()));}});t1.join();t2.join();Singleton::GetInstance()->Print();return 0;
}

5.3 饿汉模式VS懒汉模式

饿汉模式

  • 优点:简单(相较懒汉模式)。
  • 缺点:一个程序中有多个单例,并且有先后创建初始化顺序要求时,饿汉模式无法控制单例对象初始化的先后顺序;饿汉单例类对象初始化时任务多,会影响程序启动速度,并且当暂时用不到时却占用了资源!!

懒汉模式

  • 优点: 多个单例实例启动顺序可以自由控制;当需要时才将资源加载到程序中来,不影响启动速度。
  • 缺点:相对复杂,需要处理好线程安全问题。

单例对象的释放问题

一般情况下,单例对象是不需要释放的。因为整个程序运行期间都可能使用它,单例对象在进程正常结束后,也会释放资源。
有些特殊场景需要释放,比如:单例对象析构时,要进行一些持久化(往文件、数据库写数据等)操作。

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

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

相关文章

PostgreSQL学习:关于PostgreSQL以及认证

1、关于PostgreSQL PostgreSQL&#xff08;简称PG&#xff09;是强大的企业级开源关系数据库&#xff0c;世界排名第四&#xff0c;前三位Oracle 、SQLServer、MySQL都是商业数据库或受商业主体的控制&#xff0c;PG是学术社区开源数据库&#xff0c;开源协议自由度非常高&…

免费开源人脸识别系统,支持RESTful API

简介 CompreFace 是一个免费开源的人脸识别项目&#xff0c;您不需要具备机器学习技能就能安装设置和使用 CompreFace&#xff0c;官方提供了基于 docker 的部署方法&#xff0c;可以方便地部署在本地或者云端服务器上。 CompreFace 提供了 RESTful API&#xff0c;用于人脸识别…

Springboot 多环境切换 方法

准备工作 假设系统中有以下几个yml文件&#xff1a; application.ymlapplication-dev.ymlapplication-prode.ymlapplication-test.yml 方法一&#xff1a;在Active Profiles:输入dev 启动效果&#xff1a; 方法二&#xff1a;在Environment variables: 输入spring.profile…

Dijkstra算法在《庆余年》中的应用:范闲的皇宫之旅

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…

实现计时器

一、计时器 Java的标准库中,为我们提供了 计时器 的实现,让我们先来试着使用它. public static void main(String[] args) throws InterruptedException{Timer timer new Timer();timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println("hell…

GPT‑4o普通账户也可以免费用

网址 https://chatgpt.com/ 试了一下&#xff0c;免费的确实显示GPT‑4o的模型&#xff0c;问了一下可以联网&#xff0c;不知道能不能通过插件出图 有兴趣的可以试试

【诚邀加入】obdiag SIG:共筑OceanBase敏捷诊断,让问题排查变得更快更容易!

亲爱的OceanBase用户、技术爱好者及专业人士&#xff1a; 我们怀着无比激动的心情&#xff0c;正式向您发出加入obdiag SIG&#xff08;OceanBase敏捷诊断工具特别兴趣小组&#xff09;的诚挚邀请&#xff01;obdiag SIG 是一个专注于 OceanBase 敏捷诊断工具研发、推广及生态…

python 庆余年2收视率数据分析与可视化

为了对《庆余年2》的收视率进行数据分析与可视化&#xff0c;我们首先需要假设有一组收视率数据。由于实际数据可能无法直接获取&#xff0c;这里我们将使用模拟数据来演示整个过程。 以下是一个简单的步骤&#xff0c;展示如何使用Python&#xff08;特别是pandas和matplotli…

Acrobat Pro DC 2024 Mac软件安装包下载PDF2024 Mac安装教程

安装 步骤 1&#xff0c;双击打开下载好的安装包。 2&#xff0c;选择acrobat dc installer.pkg双击启动安装程序。 3&#xff0c;点击继续。 4&#xff0c;点击继续。 5&#xff0c;点击继续。 6&#xff0c;点击安装。 7&#xff0c;输入电脑密码。 8&#xff0c;软件安装中…

“手撕”String类+练习题

一、什么是String类 简单讲&#xff1a;是一个类&#xff01;创建字符串和字符串方法的类。 用 圈起来的叫字符&#xff0c;比如&#xff1a;a,b....里面只能有一个char类型的字符。 用" "圈起来的叫字符串&#xff0c;比如&#xff1a;"abc"..里面可以连…

【java】websocket对接微软语音实时识别

目录 1. pom依赖2. websocket接口3. 自定义输入流4. 自定义控制5. 自定义语音流6. 说明 1. pom依赖 <dependency><groupId>com.microsoft.cognitiveservices.speech</groupId><artifactId>client-sdk</artifactId><version>1.37.0</ve…

C# Stack用法

C#中的Stack&#xff08;堆栈&#xff09;是一种后进先出&#xff08;LIFO, Last In First Out&#xff09;的数据结构&#xff0c;用于在顶部添加和移除元素。Stack类位于System.Collections.Generic命名空间中&#xff0c;它允许存储特定类型的对象。以下是一些基本的Stack用…

如何搭建Sphinx文档

环境准备 Linux CentOS 7 方案 搭建一个文档网站&#xff0c;本文档使用的是tomcatsphinx。 Tomcat可以快速搭建出http服务&#xff0c;也可以使用apache httpd。 Sphinx作为文档网页自动生成工具&#xff0c;可以从reStructured文档转换为html文件。 Tomcat安装 创建/…

华为设备WLAN配置之AP上线

WLAN基础配置之AP上线 配置WLAN无线网络的第一阶段&#xff0c;AP上线技术&#xff1a; 实验目标&#xff1a;使得AP能够获得来自AC的DHCP地址服务的地址&#xff0c;且是该网段地址池中的IP。 实验步骤&#xff1a; 1.把AC当作三层交换机配置虚拟网关 sys Enter system view,…

PaddlePaddle----基于paddlehub的文字识别

识别的代码 要使用 PaddleHub 进行 OCR 文本识别&#xff0c;您可以使用 PaddleHub 提供的预训练模型 chinese_ocr_db_crnn_mobile。以下是一个示例 Python 代码&#xff0c;演示如何使用 PaddleHub 进行 OCR 文本识别&#xff1a; import paddlehub as hub import cv2# 加载预…

note-网络是怎样连接的1 浏览器内部

助记提要 常见浏览器使用的协议 4种HTTP方法 8种响应状态码类型 5种5种IP地址的含义域名查找的基本原理协议栈发送消息的4个阶段 第1章 浏览器生成消息 探索浏览器内部 1 浏览器输入的URL 常见协议 http(访问Web服务器)&#xff0c;ftp(从ftp服务器下载和上传)&#xff0c;f…

【XuperChain】一、搭建第一条区块链节点

一、准备环境&#xff1a; 下载git和golang即可 apt install git apt install golang二、拉取代码&#xff0c;编译XuperChain 通过此命令拉取XuperChain源码到本地 git clone https://github.com/xuperchain/xuperchain.git 拉取成功后&#xff0c;会代码保存到了xuperChain…

使用Python生成一束玫瑰花

520到了&#xff0c;没时间买花&#xff1f;我们来生成一个电子的。 Python不仅是一种强大的编程语言&#xff0c;用于开发应用程序和分析数据&#xff0c;它也可以用来创造美丽的艺术作品。在这篇博客中&#xff0c;我们将探索如何使用Python生成一束玫瑰花的图像。 准备工作…

【逻辑漏洞】重置/忘记密码安全问题

【逻辑漏洞】重置/忘记密码安全问题 密码重置令牌泄漏密码重置中毒电子邮件轰炸密码重置令牌生成方式太过简陋使用过期令牌用好的响应替换坏的响应尝试使用自己的重置令牌注销/密码重置中的会话失效 密码重置令牌泄漏 如果密码重置令牌包含在 URL 中&#xff0c;并且用户在请求…

野心是需要付出去达到的,而欲望就只是欲望

当你能分清楚自己的想法是野心还是欲望时&#xff0c;就没有那么焦虑了。 这是野心还是欲望&#xff1f; 理解这两者之间的区别&#xff0c;对于我们的情绪状态和生活决策有着重要的影响。本文将探讨如何区分野心与欲望&#xff0c;并分享如何通过这种理解来减少焦虑。 野心&a…