【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…...

深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...

springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...

基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...