C++:特殊类的设计(无线程)

目录

一、设计一个不能拷贝类

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

方法一:析构函数私有化

方法二:构造函数私有化

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

四、设计一个类不能被继承 

五、设计一个只能创建一个对象的类(单例模式)

1.设计模式

2.单例模式 

饿汉模式 

懒汉模式

一、设计一个不能拷贝类

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。

  • 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吗?

不行,因为delete释放对象的资源会调用对象的析构函数,而析构函数不可访问。这该怎么办呢?

既然析构函数被设置为私有,只能够在类内调用,那我们设置一个public函数调用析构函数可以吗?如下所示:

class HeapOnly
{
public:void Destroy(){this->~HeapOnly();}
private:~HeapOnly(){cout << "~HeapOnly" << endl;}
};//方法一:析构函数私有化
//因为没有析构函数,就只能用new申请资源赋值给类类型的指针。
int main()
{//HeapOnly hp1;HeapOnly* hp2 = new HeapOnly;//delete hp2;hp2->Destroy();return 0;
}

 运行一下:

调用了析构函数没有问题。

我们还可以用delete调用析构函数,

 但这种写法有些繁琐,我们可以将Destroy设置成静态的类成员函数。

 这样调用就简单一点。

注意:类的静态成员函数不能使用非静态成员变量,因为使用非静态成员变量要this,而静态成员函数中没有this。上面的静态成员函数调用的变量是ptr,ptr不是成员变量,更不是非静态成员变量,不需要this调用。delete ptr,是delete调用ptr指向空间的析构函数。

方法二:构造函数私有化

因为构造函数私有了,类外部不能直接访问,所以不能直接实例化,也不能通过new赋值给类类型指针了。

 同析构函数一样,我们也可以用间接调用构造函数的方式,创造对象指针,如下

 但是这里报错 :CreateObj显示未定义,这是为什么呢?因为CreateObj是成员函数,调用成员函数要先创建对象或对象的指针,而我们又要用CreateObj创建对象。

怎么解决这个问题呢?我们可以使用静态成员函数,

这样就行解决了。注意:这里的new调用构造函数,构造函数不需要this调用。 

但这种写法有个致命缺陷,就是可以通过拷贝构造创建栈上的变量。如下所示:

 由于是浅拷贝,所以hp4和hp3指向同一块空间。我添加一个私有成员变量check_i来核验一下,

 所以我们要把拷贝构造禁掉,

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

因为只能在栈上创建对象,所以要限制对象的创建方式,我们先将构造函数设置为私有,这样就只能通过类内部的某个函数调用构造函数,在这个函数中,我们通过在栈上创建变量,然后返回该变量, 同时要将这个函数设置为静态的,因为非静态成员要用隐式的this调用。

//设计一个只能在栈中创建对象的类
class StackOnly
{
public:static StackOnly CreateObj(){StackOnly obj;return obj;}
private:StackOnly(){cout << "StackOnly()" << endl;}int check_i = 666;
};
int main()
{StackOnly obj = StackOnly::CreateObj();return 0;
}

运行, 

这样只限制了构造函数,但可以通过new拷贝构造的对象申请堆上的空间,如下

 那可以把拷贝构造delete吗?

 会报错,因为传值返回会调用拷贝构造。

我们知道new分为两个步骤,一个是调用operator new ,一个是调用构造函数。

我们可以从operator new出手。对于每一个类,如果我们不显示实现类专属的operator new,它就会调用全局的operator new。如果我们实现类专属的operator new,如下

class StackOnly
{
public:static StackOnly CreateObj(){StackOnly obj;return obj;}//StackOnly(const StackOnly& obj) = delete;void* operator new(size_t size){cout << "void* operator new(size_t size)" << endl;return malloc(size);}
private:StackOnly(){cout << "StackOnly()" << endl;}int check_i = 666;
};
int main()
{StackOnly obj = StackOnly::CreateObj();StackOnly* ptr = new StackOnly(obj);return 0;
}

编译器就会优先调用显示实现的operator new,我们执行一下, 

确实如此。 

所以我们可以把operator new禁掉,这样new就会报错。

四、设计一个类不能被继承 

C++98:

C++11:

 

五、设计一个只能创建一个对象的类(单例模式)

1.设计模式

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。

使用设计模式的目的:增强代码的可重用性、可理解性、可靠性,促使代码编写工程化。

2.单例模式 

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

比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。 

单例模式有两种实现模式:

  • 饿汉模式 

 程序启动时就创建一个唯一的实例对象。 (main函数启动前)

但凡是对对象有限制的,比如只能在堆上、栈上创建,第一步,就是将构造函数私有化。

如果不私有化构造函数(因为要创建对象,所以不可能delete构造函数),那么就可以通过构造函数构造多个对象。如下,我们先将构造函数私有化:

class A
{
public:private:A(){}map<string, string> _dict;int _n = 0;
};
int main()
{}

现在我们要创建一个在main()函数调用前就存在的对象,还只能创建一个。我们把问题分解为两个小问题“main函数前创造”和“只能创造一个”,我们先思考前一个问题,什么变量在main()调用前就创造好了你?好像只能是全局变量。

我们试试创建A类型的全局变量_inst。

发现报错,我们没办法直接在类外面定义一个全局变量,那我们可以在类内声明一个变量再在类外定义吗?这不就是类的静态成员变量吗?

类的静态成员只能在类内声明类外定义,我们试试,

class A
{
public:private:A(){}map<string, string> _dict;int _n = 0;static A _inst;
};
A A::_inst;
int main()
{return 0;
}

我们运行一下程序,发现没有问题。

现在我们只要通过一个类的静态成员函数拿出_inst就行。(注意,一定要是静态的,不然访问非静态的成员函数需要对象或对象的指向,就需要构造对象)

class A
{
public:static A* GetInstance(){return &_inst;}
private:A(){}map<string, string> _dict;int _n = 0;static A _inst;
};
A A::_inst;

可以选择返回指针,也可以返回引用。

因为我们私有了构造函数,同时只创建一个类的静态成员变量,所以不修改当前代码,该类通过构造函数只有这一个对象(后面还要禁拷贝构造)。(同样的,如果定义了两个静态成员变量,该类就可以有两个对象)

那么我们怎么给这个对象添加数据呢?

这本质就是访问其他成员变量(非静态的成员变量),由于A中的成员变量都是私有的,所以我们只要间接的通过成员函数访问私有成员变量即可。

class A
{
public:static A* GetInstance(){return &_inst;}void Add(const string& key,const string& value){_dict[key] = value;}private:A(){}map<string, string> _dict;int _n = 0;static A _inst;
};
A A::_inst;

我们还可以写一个“打印函数”,

class A
{
public:static A* GetInstance(){return &_inst;}void Add(const string& key,const string& value){_dict[key] = value;}void Print(){for (auto& kv : _dict){cout << kv.first << ":" << kv.second << endl;}cout << endl;}
private:A(){}map<string, string> _dict;int _n = 0;static A _inst;
};
A A::_inst;

我们开始测试一下这个类。

int main()
{A::GetInstance()->Add("sort", "排序");A::GetInstance()->Add("left", "左边");A::GetInstance()->Add("right", "右边");A::GetInstance()->Print();return 0;
}

输出:

 我们还需要禁掉拷贝构造,不然可以通过拷贝构造创建对象,如下所示,

这样就创造了两个不同的对象。 所以我们要把拷贝构造禁掉,如果不需要自己给自己赋值,赋值重载也可以禁掉。

饿汉模式的优点:实现简单。

饿汉模式的缺点:1.可能会导致进程启动慢;

                            2.  如果有两个单例,A类单例和B类单例,分别在不同的文件中,且要区分启动的先后顺序,饿汉模式无法控制启动的先后顺序。

懒汉模式

既然懒汉模式是需要时才用,那我们可以将对象的定义到函数中,调用该函数才会真正创建对象。

为了保证只创建一个对象,我们可以用指针的接受new的对象,这样在函数可以根据指针是否为空,判断是否要创建对象,代码如下,

//懒汉模式:第一次使用时再创建(现吃现用)
class B
{
public:static B* GetInstance(){if (_inst == nullptr){_inst = new B;}return _inst;}void Add(const string& key, const string& value){_dict[key] = value;}void Print(){for (auto& kv : _dict){cout << kv.first << ":" << kv.second << endl;}cout << endl;}
private:B(){}B(const B& a) = delete;B& operator=(const B& a) = delete;map<string, string> _dict;int _n = 0;static B* _inst;
};
B* B::_inst = nullptr;
int main()
{B::GetInstance()->Add("sort", "排序");B::GetInstance()->Add("left", "左边");B::GetInstance()->Add("right", "右边");B::GetInstance()->Print();return 0;
}

执行,

 既然我们是new出一个对象,该资源怎么释放呢?其实new的懒汉对象一般不需要释放,进程正常结束会释放资源。(这里是指系统在进程结束后释放资源)

那么如果进程不结束,或者要提前释放资源,或者要在main()函数结束后释放资源呢?

我们可以定义一个释放资源的函数,外部可以直接调用释放的,

static void DelInstance()
{if (_inst){delete _inst;_inst = nullptr;}
}

 这样我们可以随时调用这个函数释放资源。

 同时我们可以定义一个内部类,以及声明一个该类的静态成员变量,然后在类外定义这个变量。内部类的析构函数封装DelInstance(),这样在main()函数运行结束后,编译器会调用内部类静态全局变量的析构函数,也可以达到清理外部类申请资源的作用。

class B
{
public:static B* GetInstance(){if (_inst == nullptr){_inst = new B;}return _inst;}void Add(const string& key, const string& value){_dict[key] = value;}void Print(){for (auto& kv : _dict){cout << kv.first << ":" << kv.second << endl;}cout << endl;}static void DelInstance(){if (_inst){delete _inst;_inst = nullptr;cout << "delete" << endl;}}
private:B(){}B(const B& a) = delete;B& operator=(const B& a) = delete;map<string, string> _dict;int _n = 0;static B* _inst;class gc{public:~gc(){DelInstance();}};static gc _gc;
};
B* B::_inst = nullptr;
B::gc B::_gc;
int main()
{B::GetInstance()->Add("sort", "排序");B::GetInstance()->Add("left", "左边");B::GetInstance()->Add("right", "右边");B::GetInstance()->Print();return 0;
}

运行,

我们没有显示调用 DelInstance(),但还是释放资源了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/41280.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数学建模MATLAB绘图大全

最近快要开始一年一度的数学建模竞赛啦&#xff0c;接下来争取每天更一篇数学建模算法&#xff01;&#xff08;当然这是理想状态下&#xff09;&#xff0c;今天就先更一些MATLAB常用的绘图吧&#xff0c;论文赏心悦目的关键就在于丰富多彩的图&#xff0c;好看的图一定会成为…

景区智慧公厕,剩余厕位显示,公厕环境监测。

在当今科技飞速发展的时代&#xff0c;景区的服务设施也在不断升级和创新。其中&#xff0c;景区智慧公厕的出现无疑为游客带来了极大的便利。特别是剩余厕位显示和公厕环境监测这两大功能&#xff0c;更是显著提升了景区公厕的管理水平和游客的使用体验。 剩余厕位显示功能是景…

磁钢生产领域上下料解决方案

随着智能制造技术的不断革新&#xff0c;磁钢生产领域正逐步引入自动化生产线。然而&#xff0c;传统的人工上下料方式存在诸多问题&#xff0c;难以满足现代生产需求。富唯智能提出了一款复合机器人磁钢上下料解决方案&#xff0c;通过先进的自动化技术&#xff0c;提高生产效…

一款纯 js 实现的大模型应用服务 FastGPT 解读

背景介绍 最近被不同的人安利了 FastGPT 项目&#xff0c;实际上手体验了一下&#xff0c;使用流程类似之前调研过的 Dify, 包含的功能主要是&#xff1a;任务流的编排&#xff0c;知识库管理&#xff0c;另外还有一些外部工具的调用能力。使用页面如下所示&#xff1a; 实际…

前端实现坐标系转换

一、地理坐标系和投影坐标系 地理坐标系和投影坐标系是地理信息系统&#xff08;GIS&#xff09;中常见的两种坐标系统&#xff0c;它们用于描述和定位地球表面上的点和区域&#xff0c;但在实现方式和应用场景上有所不同。 1. 地理坐标系&#xff08;Geographic Coordinate …

【CentOS 7.6】Linux版本 portainer本地镜像导入docker安装配置教程,不需要魔法拉取!(找不着镜像的来看我)

吐槽 我本来根本不想写这篇博客&#xff0c;但我很不解也有点生气&#xff0c;CSDN这么大没有人把现在需要魔法才能拉取的镜像放上来。 你们都不放&#xff0c;根本不方便。我来上传资源。 portainer-ce-latest.tar Linux/amd64 镜像下载地址&#xff1a; 链接&#xff1a;h…

Chapter11让画面动起来——Shader入门精要学习笔记

Chapter11让画面动起来 一、Unity Shader中的内置变量&#xff08;时间篇&#xff09;二、纹理动画1.序列帧动画2.滚动背景 三、顶点动画1.流动的河流2.广告牌3.注意事项①批处理问题②阴影投射问题 一、Unity Shader中的内置变量&#xff08;时间篇&#xff09; Unity Shader…

东北财税之星:董女士的家乡创业记

乐财业智慧财税赋能平台&#xff0c;是一个帮助财税机构专业提升、业务增长&#xff0c;让财税生意更好做的综合赋能平台。聚焦财税公司业绩增长&#xff0c;预计2027年帮助2000家财税合伙人利润增长300%&#xff0c;致力打造轻量化、批量化、智能化的”业财税“一体财税服务生…

ARCGIS PRO 要素标注

一、普通模式 1、标注&#xff1a;名称和面积&#xff08;无分数线&#xff09; 语言&#xff1a;Arcade $feature.QLR \nRound($feature.Shape_Area,2) 语言&#xff1a;vbscript [QLR] & " " & Round([Shape_Area],2) 2、标注&#xff1a;名称…

ChatGPT如何提升论文写作(附指令集合)

先讲前提&#xff1a; ChatGPT无论是3.5还是4.0都存在非常严重的幻觉问题&#xff0c;目前ChatGPT无法替代搜索引擎。 如果你希望得到更加优质的体验&#xff0c;请用GPT-4.0&#xff0c;幻觉问题上比3.5大幅降低 ChatGPT中文版&#xff0c;一站式AI创作平台​aibox365.com …

Python | Leetcode Python题解之第203题移除链表元素

题目&#xff1a; 题解&#xff1a; # Definition for singly-linked list. # class ListNode: # def __init__(self, val0, nextNone): # self.val val # self.next next class Solution:def removeElements(self, head: ListNode, val: int) -> Li…

因版本冲突导致logback的debug日志不打印

因框架调整&#xff0c;降级了logback的版本号&#xff0c;由1.3.12降级为1.2.11&#xff08;因框架限制&#xff0c;只能采用1.2版本&#xff09;&#xff0c;降级后发现debug日志无法打印出来&#xff0c;logback.xml配置文件不生效。后排查发现是与slf4j的版本兼容问题 依赖…

一种频偏估计与补偿方法

一种简易的频偏估计补偿方法&#xff0c;使用QAM等信号。估计精度受FFT长度限制&#xff0c;可以作为粗频偏估计。 Nfft 1024; % FFT长度 N 10*Nfft; % 仿真符号数 M 16; % 调制QAM16 freq 1e…

如何选择视频号矩阵系统源码:关键要素与决策指南

在短视频和直播内容迅速崛起的今天&#xff0c;视频号矩阵系统源码成为了企业和个人创作者高效管理视频内容的重要工具。选择合适的视频号矩阵系统源码&#xff0c;可以极大提升内容发布的效率和质量&#xff0c;同时优化用户体验。本文将提供一套选择视频号矩阵系统源码的指南…

MYSQL篇二:数据库的操作

文章目录 1. 创建数据库1.1 查看数据库列表1.2 创建与删除数据库 2. 数据的编码问题3. 字符集和校验规则3.1 查看系统默认字符集以及校验规则3.2 查看数据库支持的字符集3.3 查看数据库支持的字符集校验规则3.4 校验规则对数据库的影响 4. 操纵数据库4.1 查看当前是哪一个数据库…

小程序渗透测试的两种方法——burpsuite、yakit

首先呢主要是配置proxifier&#xff0c;找到小程序的流量&#xff0c;然后使用burpsuite或者yakit去抓包。 一、使用burpsuiteproxifier的抓包测试 1、先配置proxifier&#xff0c;开启http流量转发 勾选确定 2、配置burp对应代理端口&#xff0c;选择profile&#xff0c;点…

《梦醒蝶飞:释放Excel函数与公式的力量》8.7 STDEV函数

8.7 STDEV函数 STDEV函数是Excel中用于计算一组数值的标准偏差的函数。标准偏差是统计学中的一个重要指标&#xff0c;用于衡量数据集中各数值偏离平均值的程度。它反映了数据的离散程度或波动大小。 8.7.1 函数简介 STDEV函数用于返回样本数据的标准偏差&#xff0c;标准偏…

软件测试面试1000问(含答案)

1、自动化代码中,用到了哪些设计模式? 单例设计模式工厂模式PO设计模式数据驱动模式面向接口编程设计模式 2、什么是断言( Assert) ? 断言Assert用于在代码中验证实际结果是不是符合预期结果&#xff0c;如果测试用例执行失败会抛出异常并提供断言日志 3、什么是web自动化…

地图下载工具

1 概述 做仿真的&#xff0c;一般都要用到地图。各大地图厂商&#xff0c;都提供了地图测试接口。只不过&#xff0c;这些接口有限制&#xff0c;用多了就容易被封IP。于是我写了一个瓦片地图下载工具&#xff0c;把地图下载到本地&#xff0c;就可以愉快的玩耍了。 2 操作 …

文字识别技术升级:Airtest与PaddleOCR模型的协作小技巧

一、前言 在进行自动化测试的过程中&#xff0c;ocr文字识别一直是大家最想要实现以及最需要的能力&#xff0c;今天就来介绍一个由百度飞浆提供的一个免费的ocr识别库——PaddleOCR&#xff0c;以及探讨一下&#xff0c;PaddleOCR与Airtest协作能擦出怎么样的火花~ 二、Padd…