当前位置: 首页 > news >正文

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

文章目录

  • 一、设计模式概念
  • 二、设计一个不能被拷贝的类
  • 三、设计一个只能在堆上创建对象的类
    • 3.1 私有构造
    • 3.2 私有析构
  • 四、设计一个只能在栈上创建对象的类
  • 五、设计不能被继承的类
  • 六、单例模式❗️❗️
    • 6.1 饿汉模式
    • 6.2 懒汉模式
      • 6.2.1 线程安全问题
      • 6.2.2 新写法

一、设计模式概念

设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。
根本原因是为了代码复用,增加可维护性。

设计模式的例子:迭代器模式

二、设计一个不能被拷贝的类

拷贝一共就只有两个场景,一个是拷贝构造,一个是赋值运算符重载。所以我们想要设计出一个不能被拷贝的类只需要让外部无法调用这两个函数即可。

在C++98中,我们的方法是将拷贝构造和赋值运算符重载只声明不定义并且将权限设置为私有

class anti_copy
{
public:anti_copy(){}
private:anti_copy(const anti_copy& ac);anti_copy& operator=(const anti_copy& ac);
};

设计原因:
1️⃣ 私有:如果声明成共有,那么就可以在类外面实现定义。
2️⃣ 只声明不定义:因为如果不声明编译器会默认生成这两个的默认成员函数。而不定义是因为该函数不会被调用,就不用写了,这样编译的时候就会出现链接错误。

而在C++11中引入了关键字——delete。
如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。即使权限是共有也无法调用已删除的函数。

class anti_copy
{
public:anti_copy(){}anti_copy(const anti_copy& ac) = delete;anti_copy& operator=(const anti_copy& ac) = delete;
private:
};

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

3.1 私有构造

首先要把构造函数给私有,不然这个类就可以在任意位置被创建。而构造函数被私有了以后我们怎么创建对象呢?
我们可以在定义一个成员函数,让这个函数在堆上申请空间,但我们知道必须现有对象才能调用成员函数。所以我们就把这个函数设置成静态成员函数

class OnlyHeap
{
public:static OnlyHeap* GetObj(){return new OnlyHeap;}
private:OnlyHeap(){}
};

但是这样也不完全对,如果我们这么写

class OnlyHeap
{
public:static OnlyHeap* GetObj(){return new OnlyHeap;}
private:OnlyHeap(){}
};int main()
{OnlyHeap* hp1 = OnlyHeap::GetObj();OnlyHeap hp2(*hp1);return 0;
}

这里的hp2就是栈上的对象。所以我们也要把拷贝构造给封住

class OnlyHeap
{
public:static OnlyHeap* GetObj(){return new OnlyHeap;}OnlyHeap(const OnlyHeap& hp) = delete;
private:OnlyHeap(){}
};

3.2 私有析构

class OnlyHeap
{
public:OnlyHeap(){}OnlyHeap(const OnlyHeap& hp) = delete;
private:~OnlyHeap(){}
};int main()
{OnlyHeap hp1;// errorOnlyHeap* hp2 = new OnlyHeap;return 0;
}

这里的hp1就不能创建成功,因为对象销毁的时候会调用析构函数,但是这里的析构是私有的,所以该对象无法调用

但是我们要销毁hp2该怎么办呢?
我们可以定义一个成员函数显示调用析构函数

class OnlyHeap
{
public:OnlyHeap(){}OnlyHeap(const OnlyHeap& hp) = delete;void Destroy(){this->~OnlyHeap();}
private:~OnlyHeap(){}
};int main()
{OnlyHeap* hp2 = new OnlyHeap;hp2->Destroy();return 0;
}

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

为了不让这个类随便定义出对象,首先要把构造函数私有。然后跟上面只能在堆上创建对象的方法相似,定义出一个静态成员函数返回栈上创建的对象。

class StackOnly
{
public:static StackOnly GetObj(){return StackOnly();}
private:StackOnly(){}
};int main()
{StackOnly hp = StackOnly::GetObj();return 0;
}

但是这里有一个问题,无法防止创建静态对象:

static StackOnly hp2 = StackOnly::GetObj();

五、设计不能被继承的类

在C++98,为了不让子类继承,我们可以把构造函数私有化,因为子类需要先调用父类的构造函数初始化父类的那一部分成员。

class NoInherit
{
public:
private:NoInherit(){}
};

而在C++11中引入的新的关键字final,被final关键字修饰的类不能被继承。

class NoInherit final
{
public:
private:
};

六、单例模式❗️❗️

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。

单例模式的特点就是全局只有一个唯一对象。

6.1 饿汉模式

怎么能做到全局只是用一个对象呢,比方说我们现在想要实现一个英汉字典,首先我们要把构造函数私有,不然无法阻止创建对象。然后我们可以在类里面定义一个自己类型的静态成员变量,作用域是全局的。因为对比定义在外边的静态成员变量,内部的可以调用构造函数。
这里要注意把拷贝也要封住

class Singleton
{
public:static Singleton& GetObj(){return _s;}void insert(const std::string& s1, const std::string& s2){_dict[s1] = s2;}void Print(){for (auto& e : _dict){cout << e.first << "->" << e.second << endl;}}// 防拷贝Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;
private:Singleton(){}std::map<std::string, std::string> _dict;
private:static Singleton _s;// 声明
};Singleton Singleton::_s;// 定义int main()
{Singleton::GetObj().insert("corn", "玉米");Singleton& dic1 = Singleton::GetObj();dic1.insert("apple", "苹果");dic1.insert("banana", "香蕉");Singleton& dic2 = Singleton::GetObj();dic2.insert("pear", "梨");dic2.Print();return 0;
}

在这里插入图片描述
饿汉模式有什么特点呢?

它会在一开始(main之前)就创建对象。

饿汉模式有什么缺点呢?

1️⃣ 如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢
2️⃣ 多个单例类之间如果有依赖关系饿汉模式就无法控制,比方说要求A类初始化时必须调用B,但是饿汉无法控制先后顺序

所以针对这些问题,就有了懒汉模式

6.2 懒汉模式

第一次使用实例对象时,创建对象(用的时候创建)。进程启动无负载。多个单例实例启动顺序自由控制。

我们可以直接对上面饿汉模式的代码进行修改,把静态成员变量变成指针。然后把获取的函数改变一下:

static Singleton& GetObj(){// 第一次调用才会创建对象if (_s == nullptr){_s = new Singleton;}return *_s;}

整体代码:

class Singleton
{
public:static Singleton& GetObj(){// 第一次调用才会创建对象if (_s == nullptr){_s = new Singleton;}return *_s;}void insert(const std::string& s1, const std::string& s2){_dict[s1] = s2;}void Print(){for (auto& e : _dict){cout << e.first << "->" << e.second << endl;}}// 防拷贝Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;
private:Singleton(){}std::map<std::string, std::string> _dict;
private:static Singleton* _s;// 声明
};Singleton* Singleton::_s = nullptr;// 定义

6.2.1 线程安全问题

上面的代码存在问题,当多个线程同时调用GetObj(),就会创建多个对象。所以为了线程安全我们要加锁。为了保证锁自动销毁,我们可以自定义一个锁。

template <class Lock>
class LockAuto
{
public:LockAuto(Lock& lk): _lk(lk){_lk.lock();}~LockAuto(){_lk.unlock();}
private:Lock& _lk;
};class Singleton
{
public:static Singleton& GetObj(){// 第一次调用才会创建对象if (_s == nullptr)// 只有第一次才用加锁{LockAuto<mutex> lock(_mutex);if (_s == nullptr){_s = new Singleton;}}return *_s;}void insert(const std::string& s1, const std::string& s2){_dict[s1] = s2;}void Print(){for (auto& e : _dict){cout << e.first << "->" << e.second << endl;}}// 防拷贝Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;
private:Singleton(){}std::map<std::string, std::string> _dict;
private:static Singleton* _s;// 声明static mutex _mutex;// 锁
};Singleton* Singleton::_s = nullptr;// 定义
mutex Singleton::_mutex;// 定义

6.2.2 新写法

class Singleton
{
public:static Singleton& GetObj(){static Singleton dic;return dic;}void insert(const std::string& s1, const std::string& s2){_dict[s1] = s2;}void Print(){for (auto& e : _dict){cout << e.first << "->" << e.second << endl;}}// 防拷贝Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;
private:Singleton(){}std::map<std::string, std::string> _dict;
};

这里就用了静态局部变量只会在第一次定义的时候初始化。在C++11之前是不能保证线程安全的,但是C++11之后就可以了。



相关文章:

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

文章目录一、设计模式概念二、设计一个不能被拷贝的类三、设计一个只能在堆上创建对象的类3.1 私有构造3.2 私有析构四、设计一个只能在栈上创建对象的类五、设计不能被继承的类六、单例模式❗️❗️6.1 饿汉模式6.2 懒汉模式6.2.1 线程安全问题6.2.2 新写法一、设计模式概念 …...

基于YOLOv5的水下海洋目标检测

摘要&#xff1a;水下海洋目标检测技术具有广泛的应用前景&#xff0c;可以用于海洋环境监测、海洋资源开发、海洋生物学研究等领域。本文提出了一种基于 YOLOv5 的水下海洋目标检测方法&#xff0c;使用数据增强方法进行了大量实验&#xff0c;并与其他方法进行了对比&#xf…...

磁盘这列(Raid)

RAID介绍 RAID技术通过把多个硬盘设备组合成一个容量更大的、安全性更好的磁盘阵列。把数据切割成许多区段后分别放在不同的物理磁盘上&#xff0c;然后利用分散读写技术来提升磁盘阵列整体的性能&#xff0c;同时把多个重要数据的副本同步到不同的物理设备上&#xff0c;从而…...

Oracle之PL/SQL存储过程与函数练习题(七)

1.创建一个存储过程&#xff0c;以员工号为参数&#xff0c;输出该员工的工资2.创建一个存储过程&#xff0c;以员工号为参数&#xff0c;修改该员工的工资。若该员工属于10号部门&#xff0c;则工资增加150&#xff1b;若属于20号部门&#xff0c;则工资增加200&#xff1b;若…...

C++入门教程||C++ 基本的输入输出||C++ 数据结构

C 基本的输入输出 C 基本的输入输出 C 标准库提供了一组丰富的输入/输出功能&#xff0c;我们将在后续的章节进行介绍。本章将讨论 C 编程中最基本和最常见的 I/O 操作。 C 的 I/O 发生在流中&#xff0c;流是字节序列。如果字节流是从设备&#xff08;如键盘、磁盘驱动器、…...

线性表——顺序表

文章目录一&#xff1a;线性表二&#xff1a;顺序表1&#xff1a;概念与结构1&#xff1a;静态顺序表2&#xff1a;动态顺序表2&#xff1a;动态顺序表的代码实现1&#xff1a;结构2&#xff1a;接口实现1&#xff1a;初始化2&#xff1a;释放内存3&#xff1a;检查容量4&#…...

第六章 Vite4+Vue3+Vtkjs 模型颜色切换、漫反射曲面颜色

一、介绍 💥 💥 Vtk里面工具非常的齐全,但是相关的文档又少之又少,只能花大量时间去阅读源码。漫反射曲面颜色是什么意思呢,Vtk可以使用漫反射曲面颜色来模拟光线在表面反射时的颜色。漫反射是一种光线与表面发生碰撞后,被散射到各个方向的现象,这种现象可以用来解释物…...

【QT学习七】QTreeWidget

目录 一、QTreeWidget 概述 二、QTreeWidget 的基本使用 2.1、创建 QTreeWidget 控件 2.2、设置 QTreeWidget 的大小和位置 2.3、设置 QTreeWidget 的列数和列标题 2.4、添加节点 2.5、读取节点 2.6、设置节点数据 2.7、自定义节点样式 三、注意事项 四、完整示例 一…...

【Linux】组管理和权限管理

目录1 Linux组的基本介绍2 文件/目录所有者2.1 查看文件的所有者2.2 修改文件所有者3 组的创建3.1 基本指令3.2 应用实例4 文件/目录 所在组4.1 查看文件/目录所在组4.2修改文件/目录所在的组5 其他组6 改变用户所在组6.1 改变用户所在的组6.2 应用实例7 权限介绍8 rwx权限详解…...

从零到一发布 NPM 包

如果你负责前端的基础能力建设&#xff0c;发布各种功能/插件包犹如家常便饭&#xff0c;所以熟悉对 npm 包的发布与管理是非常有必要的&#xff0c;故此有了本篇总结文章。本篇文章一方面总结&#xff0c;一方面向社区贡献开箱即用的 npm 开发、编译、发布、调试模板&#xff…...

uniapp国际化配置

1、创建资源文件 创建一个locale文件夹&#xff0c;新增index.js,en.json,zh-hans.json 2.配置locale文件夹中的index.js文件 import Vue from vue import VueI18n from vue-i18n// v8.x import en from ./en.json import zhHans from ./zh-Hans.json import zhHant from .…...

前端中 try-catch 捕获不到哪些异常和常见错误

在开发过程中&#xff0c;我们的目标是 0error&#xff0c;0warning。 但有很多因素并不是我们可控的&#xff0c;为了避免某块代码的错误&#xff0c;影响到其他模块或者整体代码的运行&#xff0c;我们经常会使用try-catch模块来主动捕获一些异常或者错误。 比如我们在获取…...

javaEE 初阶 — 如何构造一个 HTTP 请求

文章目录使用 form 表单标签构造1 构造 GET 请求2 构造 POST 请求使用 ajax 构造1 什么是异步2 代码中如何使用 ajax使用第三方工具构造1 postman 工具的安装2 postman 工具的使用使用 form 表单标签构造 1 构造 GET 请求 使用 form 表单构造 HTTP 请求&#xff0c;需要用到两…...

CentOS 7下安装PostgreSQL 15版本数据库(图文详细)

文章目录CentOS 7下安装PostgreSQL 15版本数据库(图文详细)1 简介1.1 概述1.2 官网2 PostgreSQL安装2.1 选定版本2.2 安装依赖2.3 执行安装2.4 初始化2.5 配置环境变量2.6 创建数据库2.6.1 进入命令行2.6.2 创建DB2.6.3 设置密码2.7 配置远程2.8 测试链接3 pgAdmin4工具安装3.1…...

代码随想录算法训练营第五十一天 | 309. 最佳买卖股票时机含冷冻期、714. 买卖股票的最佳时机含手续费

309. 最佳买卖股票时机含冷冻期 动规五部曲 1、确定dp数组以及下标的含义 dp[i][j]&#xff0c;第i天状态为j&#xff0c;所剩的最多现金为dp[i][j]。 具体可以区分出如下四个状态&#xff1a; 状态一&#xff1a;持有股票状态&#xff08;今天买入股票&#xff0c;或者是…...

中英文拼写检测纠正开源项目使用入门 word-checker 1.1.0

项目简介 word-checker 本项目用于单词拼写检查。支持英文单词拼写检测&#xff0c;和中文拼写检测。 特性说明 可以迅速判断当前单词是否拼写错误 可以返回最佳匹配结果 可以返回纠正匹配列表&#xff0c;支持指定返回列表的大小 错误提示支持 i18n 支持大小写、全角半角…...

面试如果还不会Netty,看这篇文章就够了

我们去面试的时候&#xff0c;经常被问到netty的题目。我整理了netty的32连问。小伙伴们&#xff0c;收藏起来慢慢看吧。 1. Netty是什么&#xff0c;它的主要特点是什么&#xff1f; Netty是一个高性能、异步事件驱动的网络编程框架&#xff0c;它基于NIO技术实现&#xff0…...

作为大学生,你还不会搭建chatGPT微应用吗?

目录 引言ChatGPT是什么&#xff1f;背景&#xff1a;ChatGPT敢为人先&#xff0c;打破全球僵局示例演示&#xff1a;基于ChatGPT微应用实现的条件及步骤&#xff08;1&#xff09;整体框架&#xff08;2&#xff09;搭建前的准备工作&#xff08;3&#xff09;实际搭建步骤&a…...

Three.js教程:第一个3D场景

推荐&#xff1a;将NSDT场景编辑器加入你3D工具链其他工具系列&#xff1a;NSDT简石数字孪生下面的代码完整展示了通过three.js引擎创建的一个三维场景&#xff0c;在场景中绘制并渲染了一个立方体的效果&#xff0c;为了大家更好的宏观了解three.js引擎&#xff0c; 尽量使用了…...

lua快速入门~在js基础上,知道Lua 和 Js 的不同即可

☺ lua 和 javaScript 差不多的&#xff0c;就是一些语法的细节不同&#xff0c;学过js&#xff0c;再注意一下下面的细节&#xff0c;就能上手了~ 快速入门&#xff0c;可以直接看一下菜鸟教程的lua&#xff1a;https://www.runoob.com/lua/lua-tutorial.html Lua 和 Js 的不同…...

开源可观测性平台SigNoz:一体化监控与分布式链路追踪实战

1. 项目概述&#xff1a;从可观测性痛点出发&#xff0c;为什么我们需要SigNoz在云原生和微服务架构成为主流的今天&#xff0c;一个应用可能由数十甚至上百个服务组成&#xff0c;它们分布在不同的容器、节点甚至云区域中。当用户反馈“页面加载慢”或“功能报错”时&#xff…...

OpenAgents:从零构建数据驱动的AI智能体平台实战指南

1. 项目概述&#xff1a;当AI不只是聊天&#xff0c;而是能替你“干活”的智能体最近在AI圈子里&#xff0c;一个名为“OpenAgents”的项目热度持续攀升。它不是一个简单的聊天机器人&#xff0c;也不是一个封闭的单一应用。简单来说&#xff0c;OpenAgents是一个开源的、数据驱…...

IDEA项目乱码终结指南:从UTF-8全局设置到.properties文件特殊处理

IDEA项目乱码终结指南&#xff1a;从UTF-8全局设置到.properties文件特殊处理 在Java开发中&#xff0c;编码问题就像一颗定时炸弹&#xff0c;随时可能在最意想不到的时刻引爆。特别是当项目涉及多语言支持、团队协作或接手遗留代码时&#xff0c;乱码问题往往成为开发者挥之不…...

3个关键指标揭示:你的游戏手柄响应速度是否拖了后腿?

3个关键指标揭示&#xff1a;你的游戏手柄响应速度是否拖了后腿&#xff1f; 【免费下载链接】XInputTest Xbox 360 Controller (XInput) Polling Rate Checker 项目地址: https://gitcode.com/gh_mirrors/xin/XInputTest 在竞技游戏的激烈对决中&#xff0c;每一毫秒的…...

高校实验室项目如何利用Taotoken的Token Plan套餐控制科研实验成本

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 高校实验室项目如何利用Taotoken的Token Plan套餐控制科研实验成本 对于高校实验室的科研团队和学生项目组而言&#xff0c;在探索…...

自制AVR ISP批量编程器:从ZIF插座到AVRDUDE一键烧录全攻略

1. 项目概述&#xff1a;为什么你需要一个批量编程器&#xff1f;如果你玩过Arduino或者自己做过一些基于AVR单片机的小项目&#xff0c;那么对“烧录程序”这个步骤一定不陌生。通常&#xff0c;我们是用一根USB线&#xff0c;或者一个USBasp、USBtinyISP这样的小编程器&#…...

预训练+微调实现TVA模型快速部署

重磅预告&#xff1a;本专栏将独家连载系列丛书《智能体视觉技术与应用》部分精华内容&#xff0c;该书是世界首套系统阐述“因式智能体”视觉理论与实践的专著&#xff0c;特邀美国 TypeOne 公司首席科学家、斯坦福大学博士 Bohan 担任技术顾问。Bohan先生师从美国三院院士、“…...

RK3588 PCIe拆分技术:从原理到实战的嵌入式扩展方案

1. 项目概述&#xff1a;为什么RK3588的PCIE拆分如此重要&#xff1f;如果你正在基于瑞芯微RK3588这颗旗舰级SoC开发产品&#xff0c;无论是边缘计算盒子、NAS、工业网关还是高性能平板&#xff0c;那么PCIE总线的灵活运用绝对是你绕不开的课题。RK3588提供了多达4个PCIE 3.0控…...

ncmdump终极NCM解密转换完全指南

ncmdump终极NCM解密转换完全指南 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 你是否曾遇到过这样的困扰&#xff1f;从网易云音乐下载的歌曲只能在特定播放器中播放&#xff0c;想要在其他设备上欣赏却束手无策。这种被格式限制的…...

嵌入式AI实战:从疲劳驾驶监测到医疗内窥镜的选型与落地

1. 从一场行业盛会聊起&#xff1a;嵌入式开发者的“技术集市”前几天&#xff0c;我作为飞凌嵌入式的一名老员工&#xff0c;去杭州参加了恩智浦&#xff08;NXP&#xff09;的技术日巡回研讨会。这感觉就像是我们嵌入式开发者圈子里的一个“技术大集”&#xff0c;或者说是“…...