【C++】特殊类的设计
文章目录
- 1. 设计一个类, 不能被拷贝
- 2. 设计一个类, 不能被继承
- 3. 设计一个类, 只能在堆上创建对象
- 3. 设计一个类, 只能在栈上创建对象
- 4. 创建一个类, 只能创建一个对象(单例模式)
- 饿汉模式
- 懒汉模式
1. 设计一个类, 不能被拷贝
💕 C++98方式:
在C++11之前,想要一个一个类不被拷贝,只有将拷贝构造函数
定义为私有,这样在类外就不能调用拷贝构造函数来构造对象了。但是在类内还是可以调用拷贝构造函数来构造对象。
所以正确的做法是 将拷贝构造函数定义为私有,同时拷贝构造函数只声明,不实现。这样即使在类中掉哦那个了拷贝构造函数,编译器也会将错误检查出来。
class CopyBan
{
public:CopyBan(){_ptr = new char[10]{ 0 };}~CopyBan(){delete[] _ptr;}void func(){CopyBan tmp(*this);}private:// 重写深拷贝构造函数CopyBan(const CopyBan& cb);char* _ptr;
};
💕 C++11方式:
C++11扩展delete
的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。 同时,这种方法也不再需要将拷贝构造函数定义为私有。
class CopyBan
{
public:CopyBan(){_ptr = new char[10]{ 0 };}~CopyBan(){delete[] _ptr;}CopyBan(const CopyBan& cb) = delete;private:char* _ptr;
};
2. 设计一个类, 不能被继承
💕 C++98方式:
C++98中构造函数私有化,派生类中调不到基类的构造函数,则无法调用父类的构造函数完成父类成员的初始化工作,从而达到父类不能被继承的效果。
class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit(){}
};
class subClass : public NonInherit
{
public:subClass(){}
private:int _a = 0;
};
💕 C++11方式:
使用final
关键字来修饰该类,表示该类不能被继承
class NonInherit final
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit(){}
};
3. 设计一个类, 只能在堆上创建对象
一般的类可以在三个不同的存储位置创建对象:
- 在栈上创建对象,对象出了局部作用域自动销毁
- 通过
new
关键字在堆上创建对象,对象出了局部作用域不会自动销毁,需要我们手动销毁。否则则会发生内存泄漏 - 通过
static
关键字在静态区创建对象,对象的作用域为定义时所在的局部域,而对象的生命周期伴随着整个进程,这个对象在mian调用结束后由操作系统自动回收
💕 方法一:构造函数私有化
将构造函数声明为私有,同时删除拷贝构造函数,然后提供一个静态成员函数,在该静态成员函数中完成堆对象的创建。
class HeapOnly
{
public:static HeapOnly* CreateObj(){return new HeapOnly;}HeapOnly(const HeapOnly& ho) = delete;
private:HeapOnly(){}
};
静态成员没有this指针,所以可以通过类名+域作用限定符
来进行调用而不需要通过对象调用。同时我们还需要删除拷贝构造函数,防止在类外通过下面这种取巧的方式来创建栈区或者堆区对象:
HeapOnly* pho1 = HeapOnly::CreateObj();
HeapOnly pho2(*pho1); // 通过拷贝构造函数在栈上创建对象
static HeapOnly ho3(*pho1); // 通过拷贝构造函数在静态区上创建对象
💕 方法二:析构函数私有化
将析构函数私有化,同时提供一个专门的成员函数,在该成员函数中完成堆对象的析构
class HeapOnly
{
public:HeapOnly(){}void Destroy(){this->~HeapOnly();}
private:~HeapOnly(){}
};
对于在堆上创建的对象,编译器并不会主动调用析构函数来回收资源,而是由用户手动进行delete
或者进程退出后由操作系统回收
,所以编译器并不会报错。但对于自定义类型的对象,delete会首先调用其析构函数
完成兑现资源的清理,然后再调用operator delete 释放对象的空间,所以这里我们不能使用delete关键字来手动释放new出来的对象,因为调用析构函数会失败。
所以我们需要一个Destroy成员函数,通过它来调用析构函数完成资源的清理。这个Destroy函数不需要声明为静态类型,因为只有类的对象才需要调用它。最后,我们也不需要再删除拷贝构造函数了,因为拷贝出来的栈对象或者静态对象压根儿无法创建出来。
3. 设计一个类, 只能在栈上创建对象
💕 方法一:在类中禁用operator new 和 operator delete函数
new和delete是C++中的关键字,其底层通过调用operator new 和operator delete函数来开辟和释放空间。
operator new 和 operator delete 函数是普通的全局函数
,而并非运算符重载,它们的函数名就长这样罢了。因此,我们可以在类中重载operator new和 operator delete 函数,然后将他们声明为删除函数,这样就不能通过new和delete再堆上创建对象了,但是我们仍然可以在静态区创建对象,与类的要求不符。
class StackOnly
{
public:StackOnly(int x = 0):_x(x){}~StackOnly(){}void* operator new(size_t size) = delete;void operator delete(void* p) = delete;
private:int _x;
};
💕 方法二:构造函数私有化
,提供一个在栈上创建对象的静态成员函数
这种方式和设计一个 只能在堆上创建对象的类的思路是一样的。但是不能删除拷贝构造函数,否则就不能通过下面这种方式构造栈对象了。
StackOnly st = StackOnly::CreateObj();
但是不禁用拷贝构造函数又会导致可以通过拷贝构造函数创建出静态区上的对象,所以我们设计出的只能在栈上创建对象的类是有缺陷的。
class StackOnly
{
public:static StackOnly CreateObj(int x){return StackOnly(x);}
private:StackOnly(int x = 0):_x(x){}int _x;
};int main()
{StackOnly st1 = StackOnly::CreateObj(1);return 0;
}
4. 创建一个类, 只能创建一个对象(单例模式)
设计模式(Design Pattern)
是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式有两种实现模式,饿汉模式和懒汉模式。
饿汉模式
饿汉模式是将构造函数私有,然后删除拷贝构造函数和赋值运算符重载函数,由于单例模式全局只允许有一个唯一对象,所以我们可以定义一个静态类对象作为类的承运,然后提供一个GetInstance
接口来获取这个静态类对象。静态类对象需要在类内声明,类外定义,定义时需要指定类域。同时,GetInstance接口也必须是静态函数。
饿汉模式的特点是在类加载的时候就创建单例对象,因此其实梨花在程序运行之前(main函数调用之前就已经完成)实现如下:
class Singleton
{
public:static Singleton* GetInstance(){return _ins;}Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;private:// 限制在类外面随意创建对象Singleton(){}static Singleton* _ins;
};Singleton* Singleton::_ins = new Singleton;
因为饿汉模式在main函数前就被创建,所以它不存在线程安全问题,但是它也存在一些缺点
:
- 有的单例对象构造十分耗时或者需要占用很多资源,比如加载插件、 初始化网络连接、读取文件等等,会导致程序启动时加载速度慢。
- 饿汉模式在程序启动时就创建了单例对象,所以即使在程序运行期间并没有用到该对象,它也会一直存在于内存中,浪费了一定的系统资源。
- 多个单例类存在初始化依赖关系时,饿汉模式无法控制。比如A、B两个单例类存在于不同的文件中,我们要求先初始化A,再初始化B,但是A、B谁先启动初始化是由OS自动进行调度控制的,我们无法进行控制。
多线程模式下的饿汉模式:
class Singleton
{
public:static Singleton* GetInstance(){return _ins;}void Add(const string& str){_mtx.lock();_v.push_back(str);_mtx.unlock();}void Print(){_mtx.lock();for (auto& e : _v)cout << e << endl;cout << endl;_mtx.unlock();}Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;private:// 限制在类外面随意创建对象Singleton(){}vector<string> _v;static Singleton* _ins;mutex _mtx;
};Singleton* Singleton::_ins = new Singleton;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(rand()));}});thread t2([n]() {for (size_t i = 0; i < n; ++i){Singleton::GetInstance()->Add("t2线程:" + to_string(rand()));}});t1.join();t2.join();Singleton::GetInstance()->Print();return 0;
}
懒汉模式
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用 懒汉模式(延迟加载)更好。
懒汉模式
是另一种实现单例模式的方式,与饿汉模式不同的是:懒汉模式是延迟示例化的,即在第一次访问时才创建唯一的实例。
懒汉模式的实现思路是将构造函数私有化,然后提供一个静态私有指针成员来保存唯一实例的地址,并通过一个公共的静态方法来获取该实例。
class Singleton
{
public:static Singleton& GetInstance(){// 双检查加锁if (_ins == nullptr){_smtx.lock();if(_ins == nullptr)_ins = new Singleton;_smtx.unlock();}return *_ins;}static void DelInstance(){_smtx.lock();if (_ins) {delete _ins;_ins = nullptr;}_smtx.unlock();}Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;~Singleton(){// 持久化// 比如要求程序结束时,将数据写到文件,单例对象析构时持久化就比较好}
private:// 限制在类外面随意创建对象Singleton(){}static Singleton* _ins;static mutex _smtx;
};Singleton* Singleton::_ins = nullptr;
mutex Singleton::_smtx;
懒汉模式下的线程安全问题以及双检查加锁
懒汉模式也引入了新的问题:单例对象的创建线程是不安全的。对于懒汉模式来说,由于其单例对象是在第一次使用时才创建的,那么在多线程模式下,就有可能会存在多个线程并行/并发的去执行 _psins = new Singleton
语句,从而导致前面创建出来单例对象指针被后面的覆盖,最终发生内存泄露。
单例对象的资源释放与保存问题
一般来说单例对象都是不需要考虑释放的,因为不管是饿汉模式还是懒汉模式,单例对象都是全局的,全局资源在程序结束后会被自动回收 (进程退出后OS会解除进程地址空间与物理内存的映射)。但是我们也可以手动对其进行回收。需要注意的是,有时我们需要在回收资源之前将资源的相关数据保存到文件中,这种情况下我们就必须手动回收了。
- 在类中定义一个静态的 DelInstance接口,来回收与保存资源。
static void DelInstance()
{_smtx.lock();if (_ins) {delete _ins;_ins = nullptr;}_smtx.unlock();
}
- 定义一个内部的GC类,通过Singleton类中的一个静态GC类对象,使得程序在结束回收GC对象时自动调用GC类的析构从而完成资源回收与数据保存工作。避免我们忘记调用Dellnstance接口而丢失数据。
例如:
class Singleton
{
public:static Singleton& GetInstance(){if (_ins == nullptr){// 双检查加锁_smtx.lock();if(_ins == nullptr)_ins = new Singleton;_smtx.unlock();}return *_ins;}void Add(const string& str){_mtx.lock();_v.push_back(str);_mtx.unlock();}void Print(){_mtx.lock();for (auto& e : _v)cout << e << endl;cout << endl;_mtx.unlock();}static void DelInstance(){_smtx.lock();if (_ins) {delete _ins;_ins = nullptr;}_smtx.unlock();}// 单例对象回收class GC{public:~GC(){DelInstance();}};static GC _gc;Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;~Singleton(){// 持久化// 比如要求程序结束时,将数据写到文件,单例对象析构时持久化就比较好}
private:// 限制在类外面随意创建对象Singleton(){}vector<string> _v;static Singleton* _ins;mutex _mtx;static mutex _smtx;
};Singleton* Singleton::_ins = nullptr;
mutex Singleton::_smtx;
Singleton::GC Singleton::_gc;
懒汉模式的一种简单实现方式
class Singleton
{
public:static Singleton& GetInstance(){static Singleton sins;return sins;}Singleton(const Singleton& sin) = delete;Singleton& operator=(const Singleton& sin) = delete;
private:Singleton(){}
};
上面这种实现方式的缺点就是不稳定,因为只有在 C++11 及其之后的标准中局部静态对象的初始化才是线程安全的,而在 C++11 之前的版本中并不能保证。
相关文章:

【C++】特殊类的设计
文章目录 1. 设计一个类, 不能被拷贝2. 设计一个类, 不能被继承3. 设计一个类, 只能在堆上创建对象3. 设计一个类, 只能在栈上创建对象4. 创建一个类, 只能创建一个对象(单例模式)饿汉模式懒汉模式 1. 设计一个类, 不能被拷贝 💕 C98方式: 在C11之前&a…...
机器学习:PCA(Principal Component Analysis主成分)降维
参考:PCA降维原理 操作步骤与优缺点_TranSad的博客-CSDN博客 PCA降维算法_偶尔努力翻身的咸鱼的博客-CSDN博客 需要提前了解的数学知识: 一、PCA的主要思想 PCA,即主成分分析方法,是一种使用最广泛的数据降维算法。PCA的主要思想…...
linux服务器slab缓存回收方案设计
背景 自己写的回收slab内存ko,insmod报错“shrink_slab:unknown symbol _x86_indirect_thunk_rax(err 0)””; 分析 1.名词解释 在 x86 架构中,函数调用通常使用 call 指令来直接跳转到目标函数的地址。但是,当需要通过函数指针或动态链接调用函数时,就需要使用__x86_…...
Apache Spark 的基本概念
Apache Spark 是一种快速、可扩展、通用的数据处理引擎。它是一种基于内存的计算框架,支持分布式数据处理、机器学习、图形计算等多种计算任务。与传统的 Hadoop MapReduce 相比,Spark 具有更高的性能和更广泛的应用场景。 Spark 中的基本概念包括&…...

通讯协议介绍CoAP 协议解析
目录 1 通讯协议 2 TCP/IP 网络模型 2.1 TCP协议 2.1.1 TCP 连接过程 2.1.2 TCP 断开连接 2.1.3 TCP协议特点 2.2 UDP协议 2.2.1 UDP 协议特点 3 应用层协议简介 3.1 HTTP 协议 3.2 CoAP 协议 3.3 MQTT 协议 4 CoAP 协议详解 4.1 REST 风格 4.2 CoAP 首部分析 4…...

React 开发一个移动端项目(2)
配置基础路由 目标:配置登录页面的路由并显示在页面中 步骤: 安装路由: yarn add react-router-dom5.3.0 5 和 6 两个版本对组件类型的兼容性和函数组件支持有所改变,在这里使用的是 5。 和路由的类型声明文件 yarn add types…...

51单片机 点阵矩阵 坤坤代码
真正的黑子 #include <REGX52.H>void Delay(unsigned int xms); void _74HC595_WriteByte(unsigned char byte); void LED(unsigned char Y,DATA); void LED_Init();sbit RCKP3^5; //RCLK sbit SCKP3^6; //SRCL sbit SERP3^4; //SER //坤坤矩阵 unsigned char code D…...
Android13-图片视频选择器
在compileSDK 33 时,谷歌在安卓新增了 图片选择器 功能,支持单选、多选、选图片、视频等操作,并且不需要额外获取照片/音频权限。 具体实现如下: 1:请求 Log.d(TAG, "Build.VERSION.SDK_INT" Build.VERS…...

【问题处理】GIT合并解决冲突后,导致其他人代码遗失的排查
GIT合并解决冲突后,导致其他人代码遗失的排查 项目场景问题描述分析与处理:1. 警告分析2. 文件分析3. 问题关键4. 验证 解决策略总结 📕作者简介:战斧,从事金融IT行业,有着多年一线开发、架构经验ÿ…...

H264视频压缩格式
H264简介 H.264从1999年开始,到2003年形成草案,最后在2007年定稿有待核实。在ITU的标准里称为H.264, 在MPEG的标准里是MPEG-4的一个组成部分-MPEG-4 Part 10,又叫Advanced Video Codec,因此常常称为MPEG-4AVC或直接叫AVC。 压缩算…...

动态的中秋爱心演示送女友用python生成爱心软件文末附c++语言写法
用python生成爱心软件 用python生成动态爱心软件 目录 用python生成爱心软件 完整代码 代码解释 逐句解释 效果展示: 如何打包 c写法 完整代码 import turtledef draw_heart():love turtle.Turtle()love.getscreen().bgcolor("black")love.…...

macOS - 使用VLC
文章目录 关于 VLC安装查看帮助流媒体 MRL 语法:URL 语法:主程序 (core)音频视频截图:窗口属性: 子画面屏幕显示(OSD):字幕:覆盖:轨道设置:播放控制:默认设备:高级: 输入播放列表性能选项: 热键跳跃大小: 关于 VLC VLC media player VLC 是一款自由、开…...

java微服务项目整合skywalking链路追踪框架
skywalking官网网址:Apache SkyWalking 目录 1、安装skywalking 2、微服务接入skywalking 3、skywalking数据持久化 1、安装skywalking 下载skywalking,本篇文章使用的skywalking版本是8.5.0 Index of /dist/skywalkinghttps://archive.apache.org/…...
pandas 笔记: interpolate
一个用于填充 NaN 值的工具 1 基本用法 DataFrame.interpolate(methodlinear, *, axis0, limitNone, inplaceFalse, limit_directionNone, limit_areaNone, downcast_NoDefault.no_default, **kwargs) 2 主要参数 method 多种插值技术 linear: 默认值,使用线性插…...

应用程序接口(API)安全的入门指南
本文简单回顾了 API 的发展历史,其基本概念、功能、相关协议、以及使用场景,重点讨论了与之相关的不同安全要素、威胁、认证方法、以及十二项优秀实践。 根据有记录的历史,随着 Salesforce 的销售自动化解决方案的推出,首个 Web…...

JavaWeb概念视频笔记
学习地址:102.尚硅谷_Tomcat-Tomcat服务器和Servlet版本的对应关系_哔哩哔哩_bilibili 目录 1.JavaWeb的概念 2.Web资源的分类 3.常用的Web服务器 4.Tomcat服务器和Servlet版本的对应关系 5.Tomcat的使用 a.安装 b.目录介绍 c.如何启动 Tomcat 服务器 另一…...

网络请求【小程序】
一、get 二、post 1.获取相应数据 Page({/*** 页面的初始数据*/data: { inptValue:, isArr:[]},/*** 生命周期函数--监听页面加载*/onLoad(options) {},onSubmit(){// console.log(this.data.inptValue)//2.后台请求数据wx.request({url: https://tea.qingnian8.com/demoArt/…...
python 调用adb shell
目录 python调用 bat,启动新窗口,但是不能自动在进入shell 后执行提前设置的操作。 python启动cmd新窗口,但是不能自动在进入shell 后执行提前设置的操作。 python调用 bat,启动新窗口,但是不能自动在进入shell 后执…...
vue3 使用 vite 构建的项目打包后无法访问
解决办法: 1、安装 vitejs/plugin-legacy -D npm i vitejs/plugin-legacy -D2、vite.config.js 添加配置 import legacy from vitejs/plugin-legacy; export default defineConfig({plugins: [legacy({targets: [defaults, not IE 11]}),vue(),],base:./, // http…...

C语言指针详解(4)———找工作必看指针笔试题汇总
指针对于编程工作的重要性 C语言指针在找工作中具有重要性。以下是几个原因: 1.高效的内存管理:C语言指针可以帮助程序员高效地管理内存,包括动态内存分配和释放,以及数据的访问和操作。这对于开发性能优化的应用程序非常重要&am…...

K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...

破解路内监管盲区:免布线低位视频桩重塑停车管理新标准
城市路内停车管理常因行道树遮挡、高位设备盲区等问题,导致车牌识别率低、逃费率高,传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法,正成为破局关键。该设备安装于车位侧方0.5-0.7米高度,直接规避树枝遮…...

算法打卡第18天
从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。 示例 1: 输入:inorder [9,3,15,20,7…...

2.3 物理层设备
在这个视频中,我们要学习工作在物理层的两种网络设备,分别是中继器和集线器。首先来看中继器。在计算机网络中两个节点之间,需要通过物理传输媒体或者说物理传输介质进行连接。像同轴电缆、双绞线就是典型的传输介质,假设A节点要给…...

C++--string的模拟实现
一,引言 string的模拟实现是只对string对象中给的主要功能经行模拟实现,其目的是加强对string的底层了解,以便于在以后的学习或者工作中更加熟练的使用string。本文中的代码仅供参考并不唯一。 二,默认成员函数 string主要有三个成员变量,…...

医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor
1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…...