【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…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...

JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...

uniapp手机号一键登录保姆级教程(包含前端和后端)
目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号(第三种)后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...

手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...
深入浅出Diffusion模型:从原理到实践的全方位教程
I. 引言:生成式AI的黎明 – Diffusion模型是什么? 近年来,生成式人工智能(Generative AI)领域取得了爆炸性的进展,模型能够根据简单的文本提示创作出逼真的图像、连贯的文本,乃至更多令人惊叹的…...

9-Oracle 23 ai Vector Search 特性 知识准备
很多小伙伴是不是参加了 免费认证课程(限时至2025/5/15) Oracle AI Vector Search 1Z0-184-25考试,都顺利拿到certified了没。 各行各业的AI 大模型的到来,传统的数据库中的SQL还能不能打,结构化和非结构的话数据如何和…...