【C++】特殊类实现
一、请设计一个类,不能被拷贝
拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,
只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
- 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;//...
};
二、请设计一个类,只能在堆上创建对象
方法一:将类的析构函数私有。防止别人调用拷贝在栈上生成对象

编译报错:

栈上创建会自动调用析构,这里析构被私有了,就不能在栈上创建。
但这样在堆上创建后要进行delete也要调用析构函数,也就是虽然不能在栈上创建对象,但是同样也不能在堆上创建对象了。
解决方法:在类public中实现一个成员函数进行delete
class HeapOnly
{
public:void Destroy(){delete this;}
private:~HeapOnly(){//...}
};int main()
{//HeapOnly hp1;//static HeapOnly hp2;HeapOnly* hp3 = new HeapOnly;//delete hp3;hp3->Destroy();return 0;
}
编译成功:
![]()
方法二:
实现方式:
1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。
完整代码:
class HeapOnly
{
public:static HeapOnly* CreateObj(){return new HeapOnly;}
private:HeapOnly(){//...}HeapOnly(const HeapOnly& hp) = delete;HeapOnly& operator=(const HeapOnly& hp) = delete;
};int main()
{HeapOnly* hp3 = HeapOnly::CreateObj();//HeapOnly copy(*hp3);return 0;
}
以上代码有两个细节:
1. 如果没有提供静态的成员函数:

会发生报错,调用一个类的普通成员函数是不是需要对象去调用,那我们在堆上申请的对象又正好需要调用这个函数来生成,可是我们选择都没有对象我们如何去调用这个函数呢?所以这就纯纯是先有鸡还是先有蛋的问题了。
为了解决这个问题,我们可以将这个成员函数设置成静态的。
2. 利用拷贝构造在栈上创建报错:
虽然我们不能够直接的在栈上创建对象,但是我们可以在堆上申请一个对象之后再调用一次拷贝构造然后间接的就在栈上申请对象了。
所以我们的完整代码还要将拷贝构造和赋值重载都声明成delete禁掉,所以这里调用拷贝构造报错

三、请设计一个类,只能在栈上创建对象
方法:同上将构造函数私有化,然后设计静态方法创建对象返回即可。
下面先来看这种方式的实现:


编译报错,说明不能在堆上创建。
但是我们如果在new的时候调用拷贝构造:

编译通过:
![]()
我们发现又可以在堆上成功了,说明这种方法还有漏洞。
解决方法:下面我们来分析一下:new是由operator new + 构造组成的,所以我们只要在类中重载一个new,并将new声明成delete,就可以解决这个问题。
class StackOnly
{
public:static StackOnly CreateObj(){StackOnly st;return st;}
private:StackOnly(){//...}// 对一个类实现专属operator newvoid* operator new(size_t size) = delete;
};

运行发现报错,说明不能只有new在堆上创建对象了。
四、请设计一个类,不能被继承
- C++98方式
将构造函数私有:
基类的私有成员在派生类不可访问,而派生类要继承,需要调用基类的构造函数。所以将构造函数私有可以,让类不能被继承。
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit(){}
};
- C++11方法
final关键字,final修饰类,表示该类不能被继承。
class A final
{// ....
};
五、设计一个类,只能创建一个对象(单例模式)
设计模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。
设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
单例模式:一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
对于单例模式我们首先需要解决一个问题:如果保证全局(一个进程中)只有一个唯一实例对象
经过上面的学习我们就可以知道,要想保证全局只有一个唯一实例对象我们需要做以下两步操作
- 构造函数私有定义。拷贝构造和赋值防拷贝禁掉
- 提供一个GetInstance获取单例对象
单例模式有两种实现模式:饿汉模式与懒汉模式
饿汉模式:就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。
下面来看饿汉模式的代码:
主要有以下三个关键步骤
1、构造函数私有 2、提供获取单例对象的接口函数 3、防拷贝
经过上面的学习我们就可以知道,为了防止被创建多个对象,所以我们要1、3这俩步,构造函数私有定义。拷贝构造和赋值防拷贝禁掉。
然后我们创建单例对象, 还要给它提供获取单例对象的接口函数。
// 饿汉模式:一开始(main函数之前)就创建单例对象
// 提供一个静态指向单例对象的成员指针,初始化时new一个对象给它
// 1、如果单例对象初始化内容很多,影响启动速度
// 2、如果两个单例类,互相有依赖关系。
// 假设有A B两个单例类,要求A先创建,B再创建,B的初始化创建依赖A
namespace hungry
{class Singleton{public:// 2、提供获取单例对象的接口函数static Singleton& GetInstance(){return _sinst;}void func();void Add(const pair<string, string>& kv){_dict[kv.first] = kv.second;}void Print(){for (auto& e : _dict){cout << e.first << ":" << e.second << endl;}cout << endl;}private:// 1、构造函数私有Singleton(){// ...}// 3、防拷贝Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;map<string, string> _dict;// ...static Singleton _sinst;};Singleton Singleton::_sinst;void Singleton::func(){// _dict["xxx"] = "1111";}
}int main()
{//Singleton s1;//Singleton s2;cout << &hungry::Singleton::GetInstance() << endl;cout << &hungry::Singleton::GetInstance() << endl;cout << &hungry::Singleton::GetInstance() << endl;//Singleton copy(Singleton::GetInstance());hungry::Singleton::GetInstance().Add({ "xxx", "111" });hungry::Singleton::GetInstance().Add({ "yyy", "222" });hungry::Singleton::GetInstance().Add({ "zzz", "333" });hungry::Singleton::GetInstance().Add({ "abc", "333" });hungry::Singleton::GetInstance().Print();return 0;
}
运行结果:

可以看到单例模式只能创建了一个对象
把其他的创建屏蔽掉再看运行结果:

可以看到GetInstance()之后地址都是相同的,说明只创建了一个对象。并且能实现里面简单的功能
饿汉模式适用场景:
- 如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。
饿汉模式不适用场景:
- 如果单例类构造函数中,要做很多配置初始化工作,导致程序启动非常慢
- 如果两个单例类,互相有依赖关系。 假设有A B两个单例类,要求A先创建,B再创建,B的初始化创建依赖A。我们如何保证先创建A再创建B呢?
这个时候我们使用饿汉就不合适了,这时我们应该使用下面的懒汉模式。
懒汉模式
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
下面来看这段懒汉的代码
namespace lazy
{//懒汉模式class Singleton{public:static Singleton* GetInstance(){if (_psinst == nullptr){_psinst = new Singleton;}return _psinst;}void Print(){cout << "Print()" << _a << endl;}private:Singleton():_a(0){}//C++98 防拷贝//Singleton(const Singleton&);//Singleton& operator=(const Singleton&);//C++11 防拷贝Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;int _a;static Singleton* _psinst;};Singleton* Singleton::_psinst = nullptr;}
但是这么写是会存在一些问题的。
在多线程场景下会存在线程安全的问题,因为我们的懒汉式要用的时候才会去调用GetInstance函数再去创建那个唯一的对象。
假如说现在有两个线程,分别是线程A与线程B,然后我线程A与线程B都调用了GetInstance函数,线程A与线程B都进入了if条件判断里面,此时我们线程A如果先执行实例化对象的代码,然后返回。因为我们的线程B不知道线程A已经实例化了唯一对象,此时线程B再去调用实例化对象的代码,就会导致前面的唯一对象的地址被覆盖掉,线程A的数据会丢失,并且会有内存泄漏的风险,更严重的是此时已经产生了两个实例对象,违反了单例模式的设计思想。
那我们应如何解决上面的问题呢?我们需要进行双检查加锁。
//懒汉模式
#include <mutex>
namespace lazy
{class Singleton{public:static Singleton* GetInstance(){// 保护第一次需要加锁,后面都不需要加锁的场景,可以使用双检查加锁// 特点:第一次加锁,后面不加锁,保护线程安全,同时提高了效率if (_pinst == nullptr){_mtx.lock();if (_pinst == nullptr){_pinst = new Singleton;}_mtx.unlock();}return _pinst;}static void DelInstance(){_mtx.lock();if (_pinst){delete _pinst;_pinst = nullptr;}_mtx.unlock();}void Print(){cout << "Print()" << _a << endl;}private:Singleton():_a(0){// 假设单例类构造函数中,要做很多配置初始化}~Singleton(){// 程序结束时,需要处理一下,持久化保存一些数据}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;// 实现一个内嵌垃圾回收类 class CGarbo {public:~CGarbo(){if (_pinst){delete _pinst;_pinst = nullptr;}}};int _a;static Singleton* _pinst;static std::mutex _mtx;static CGarbo _gc;};Singleton* Singleton::_pinst = nullptr;std::mutex Singleton::_mtx;Singleton::CGarbo Singleton::_gc;
}
为什么是双检查加锁?
我们需要知道我们想加锁的地方只是第一次 _pinst为空的时候,我们想实例化一个唯一对象时才需要加锁,但是如果到了后面我们已经实例化出了对象,但是我们照样加锁了之后再去判断 _pinst是否为空,然后在多线程场景下频繁的加锁解锁会导致效率降低,并且也不推荐这样做。
而我们的双检查加锁里面的第二个if检查就是我先进行加锁,然后进行判断第一次_pinst是否为空,然后实例化一个唯一对象,等到下一次某个线程再调用GetInstance的时候,我们的第一个if检查发现pinst不为空,就不会进入第一个if条件判断的里面,因此也就不存在多线程场景下频繁加锁解锁,保护线程安全,同时提高了效率。
饿汉模式和懒汉模式的优缺点对比如下:
饿汉模式:
- 优点:实例化对象在类加载时完成,因此无须考虑多线程访问问题,可以确保实例的唯一性。
- 缺点:
- 饿汉式单例对象在类加载时就被创建,可能会造成系统资源的浪费,尤其是在对象占内存较大或对象初始化耗时较长的情况下。
- 如果有多个单例对象,他们之间有初始化依赖关系,饿汉模式也会有问题。(比如有A和B两个单例类,要求A单例先初始化,B必须在A之后初始化。那么饿汉无法保证。这种场景下面用懒汉就可以,懒汉可以先调用A::GetInstance(),再调用B::GetInstance().)
懒汉模式:
- 优点:实现了延迟加载,只有在第一次使用时才创建实例,节省了系统资源。(解决饿汉的缺点。因为他是第一次调用GetInstance时创建初始化单例对象)
- 缺点:必须考虑与处理多个线程同时访问的问题,需要进行双重检查锁定等机制及进行控制。
相关文章:
【C++】特殊类实现
一、请设计一个类,不能被拷贝 拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。 C98 将拷贝构造函数与赋值运算符重载只声明不定义…...
代码随想录打卡第四十四天|● 01 二维背包问题 ●一维背包问题-滚动数组 ● 416. 分割等和子集
什么是01背包 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。 01背包的模板 二维dp数组 dp数组的含义 dp[i][j]含义下标为【0-i】之间…...
燃气管网智能巡检系统
燃气管网维护工作繁杂,涉及人员、资源、巡检等,稍一疏忽就会使我们的工作陷入被动,可见启用燃气管网智能巡检系统是很有必要的。 燃气管网智能巡检系统综合管理智能平台,可对燃气管网数据的统一管理,实现对日常巡查、养…...
【微信小程序开发】运用WXS进行后台数据交互
🥳🥳Welcome Huihuis Code World ! !🥳🥳 接下来看看由辉辉所写的关于小程序的相关操作吧 一.wxs是什么 WXS是指"微信小程序云开发"(WeChat Mini Program Cloud Development),是由微信…...
屏幕录像推荐:Apeaksoft Screen Recorder 中文 for mac
Apeaksoft Screen Recorder 是一款功能强大的屏幕录制软件,它允许用户在 Windows 和 Mac 系统上捕捉和录制屏幕活动。无论是记录游戏过程、创建教学视频、制作演示文稿还是捕捉在线流媒体内容,该软件都提供了丰富的功能和工具。 以下是 Apeaksoft Scree…...
ALPHA开发板网络方案说明
一. 简介 正点原子 ALPHA开发板,包括我们移植的 Uboot,都是参考了 NXP(恩智浦)官方的开发板的。 I.MX6UL/ULL 内部有个以太网 MAC 外设,也就是 ENET ,需要外接一个 PHY 芯片来实现网络通信功能&#…...
[Ubuntu 20.04] HEIF图像格式与libheif库及其工具的使用
一、HEIF图像格式 HEIF 是一种高效的图像文件格式,它由 MPEG(Moving Picture Experts Group)组织制定。相较于传统的 JPEG 格式,HEIF 提供了更好的图像质量和更高的压缩率。下面是对 HEIF 格式的详细解析: 图像编码技术:HEIF 使用先进的编码技术来实现更高效的图像压缩。…...
AI驱动的未来:探索人工智能的无限潜力 | 开源专题 No.39
这一系列开源项目代表着多个领域的最新技术成果,包括深度学习、自然语言处理、计算机视觉和分布式训练。它们共同的特点是致力于教育、资源分享、开源精神、多领域应用以及性能和效率的追求,为广大开发者、研究者和学生提供了宝贵的工具和知识࿰…...
vs中C++编译未生成exe
1、新建空工程,添加main.h文件至“头文件”文件夹中,添加mian函数及实现 2、编译工程未有任何提示,不报错,不生成exe,无法执行 对比新建控制台程序发现.vcxproj文件中引用main.h文件为 无法生成: <I…...
Linux自有服务与软件包管理
服务是一些特定的进程,自有服务就是系统开机后就自动运行的一些进程,一旦客户发出请求,这些进程就自动为他们提供服务,windows系统中,把这些自动运行的进程,称为"服务" 举例:当我们使…...
Centos7中redis开机自启动设置
以下亲测实践有效。 进入以下目录 cd usr/local/redis/redis-6.2.6/utils/ 编辑修改以下文件内容 vim redis_init_script #修改redis安装启动目录 REDISPORT6379 #修改安装目录 EXEC/usr/local/redis/redis-6.2.6/src/redis-server CLIEXEC/usr/local/redis/redis-6.2.6/sr…...
STM32F4之系统滴答定时器
一、系统滴答定时器概述 传统定时器:如手机闹钟,闹钟等就是一个简单地计数器。 定时器概念:由时钟源计数器计数值组成的计数单元。 系统嘀嗒定时器首先是存在于内核里,系统嘀嗒时钟假如用的是同一个内核那么里面相关的配置&…...
P4 并发控制
文章目录 Task1 锁管理器LockTableUnLockTableLockRowUnLockRow Task2 死锁检测Task3 并发查询执行器Isolation Levelseq_scan_executorinsert_executordelete_executortransaction_manager Task1 锁管理器 LockManager类包含两个属性类,分别是LockRequest和LockRe…...
友元的介绍
实现外部类和外部函数存取类的私有成员和保护成员的方法。 一、友元函数 可访问类所有成员的外部函数 //求两点间的距离:抽象点——>求距离的函数 #include<iostream> #include<cmath> using namespace std; class Point{private:double x,y;publ…...
新手如何找到Docker容器(redis)中的持久化文件?
具体步骤 要查看Docker容器的dump.rdb和appendonly.aof文件(如果启用了AOF持久化)的位置,我们需要知道容器中Redis配置文件的内容或者容器的数据卷的挂载位置。 这里是一般步骤: 查找容器的数据卷挂载位置 使用docker inspect命令…...
python二次开发Solidworks:读取立方体的高度
在SW中新建一个零件文档,建立一个立方体,长度和宽度自定义,高度100mm,下面通过python实现读取该立方体的高度: import win32com.client as win32 import pythoncomswApp win32.Dispatch(sldworks.application) swApp.…...
NPM安装后报错:ERROR: npm v10.2.1 is known not to run on Node.js v10.24.1.
问题描述 NPM卸载高版本后安装低版本运行报错: C:\Users\Administrator>npm -v ERROR: npm v10.2.1 is known not to run on Node.js v10.24.1. This version of npm supports the following node versions: ^18.17.0 || >20.5.0. You can find the latest…...
【Vue】Element开发笔记
Element开发笔记 前言 官网 https://element.eleme.cn/#/zh-CN/component/upload 其它项目网站 https://www.cnblogs.com/qq2806933146xiaobai/p/17180878.html 表格 序号列添加 <el-table-column type"index" :index"handleIndexCalc" label&qu…...
How to install mongodb 7.0 to Ubuntu 22.04
How to install mongodb 7.0 to Ubuntu 22.04 1、安装1.1、添加gpg1.2、添加apt源1.3、更新1.4、安装 2、管理2.1、服务管理2.1.1、查看服务状态2.1.2、启动服务2.1.3、 设置服务为开机启动2.1.4、取消服务开机启动2.1.5、关闭服务2.1.6、服务重启 2.2、mongosh2.2.1、进入mong…...
AFL安全漏洞挖掘
安全之安全(security)博客目录导读 ATF(TF-A)/OPTEE之FUZZ安全漏洞挖掘汇总 目录 一、AFL简介 二、AFL的安装 三、代码示例及种子语料库 四、AFL插桩编译 五、AFL运行及测试 六、AFL结果分析 一、AFL简介 模糊测试(Fuzzing)技术作为漏洞挖掘最有…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
