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.xdnf.cn/news/1472779.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL】mysql访问

mysql访问 1.引入MySQL 客户端库2.C/C 进行增删改3.查询的处理细节4.图形化界面访问数据库4.1下载MYSQL Workbench4.2MYSQL Workbench远程连接数据库 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&a…

通过端口和进程pid查找启动文件/脚本

今天审计一个程序又让GPT给我上了一课&#xff0c;记一下笔记&#xff1a; 1、首先该程序开启了8080端口&#xff0c;使用如下命令得到pid为1817 netstat -tunlp|grep 80802、使用pid得到父进程 pstree -ps 1817输出结果如下&#xff1a; 3、看出程序是由systemd启动的&…

[单master节点k8s部署]19.监控系统构建(四)kube-state-metrics

kube-state-metrics 是一个Kubernetes的附加组件&#xff0c;它通过监听 Kubernetes API 服务器来收集和生成关于 Kubernetes 对象&#xff08;如部署、节点和Pod等&#xff09;的状态的指标。这些指标可供 Prometheus 进行抓取和存储&#xff0c;从而使你能够监控和分析Kubern…

钡铼RTU无线S270用于风力发电站机房远程状态监测和故障预警系统集成

在现代风力发电行业中&#xff0c;机房的远程监测和故障预警系统对于保障风力发电机组的稳定运行至关重要。钡铼第4代S270工业级4G远程遥测终端&#xff08;RTU&#xff09;&#xff0c;以其先进的技术和多功能应用&#xff0c;成为风力发电站机房智能化管理的理想选择。 技术…

【MySQL备份】Percona XtraBackup总结篇

目录 1.前言 2.问题总结 2.1.为什么在恢复备份前需要准备备份 2.1.1. 保证数据一致性 2.1.2. 完成崩溃恢复过程 2.1.3. 解决非锁定备份的特殊需求 2.1.4. 支持增量和差异备份 2.1.5. 优化恢复性能 2.2.Percona XtraBackup的工作原理 3.注意事项 1.前言 在历经了详尽…

uni-app组件 子组件onLoad、onReady事件无效

文章目录 导文解决方法 导文 突然发现在项目中&#xff0c;组件 子组件的onLoad、onReady事件无效 打印也出不来值 怎么处理呢&#xff1f; 解决方法 mounted() {console.log(onLoad, this.dateList);//有效// this.checkinDetails()},onReady() {console.log(onReady, this.da…

百度云智能媒体内容分析一体机(MCA)建设

导读 &#xff1a;本文主要介绍了百度智能云MCA产品的概念和应用。 媒体信息海量且复杂&#xff0c;采用人工的方式对视频进行分析处理&#xff0c;面临着效率低、成本高的困难。于是&#xff0c;MCA应运而生。它基于百度自研的视觉AI、ASR、NLP技术&#xff0c;为用户提供音视…

【C++】哈希表 ---开散列版本的实现

你很自由 充满了无限可能 这是很棒的事 我衷心祈祷你可以相信自己 无悔地燃烧自己的人生 -- 东野圭吾 《解忧杂货店》 开散列版本的实现 1 前言2 开散列版本的实现2.1 节点设计2.2 框架搭建2.3 插入函数2.4 删除函数2.5 查找操作2.6 测试 Thanks♪(&#xff65;ω&#x…

申请便宜SSL证书 Let‘s Encrypt泛域名SSL证书

在当今数字化时代&#xff0c;网络安全已成为公众和企业关注的焦点。 申请Lets Encrypt便宜泛域名SSL证书步骤 1. 登录来此加密网站&#xff0c;输入域名&#xff0c;可以勾选泛域名和包含根域。 2. 选择加密方式&#xff0c;一般选择默认就可以了&#xff0c;也可以自定义CS…

【启明智显分享】乐鑫HMI方案2.8寸触摸串口屏应用于太阳能控制器

前言 太阳能作为一种无尽的、可再生的能源&#xff0c;在现代社会的能源结构中占据着日益重要的地位。而在太阳能应用系统中&#xff0c;有一种设备是不可或缺的&#xff0c;那就是太阳能控制器。太阳能控制器在太阳能系统中起着至关重要的作用&#xff0c;它保证系统的安全和…

AntDesign上传组件upload二次封装+全局上传hook使用

文章目录 前言a-upload组件二次封装1. 功能分析2. 代码详细注释3. 使用到的全局上传hook代码4. 使用方式5. 效果展示 总结 前言 在项目中&#xff0c;ant-design是我们常用的UI库之一&#xff0c;今天就来二次封装常用的组件a-upload批量上传组件,让它用起来更方便。 a-uploa…

谷歌地图Google JS API 实现

demo实现 实现源码&#x1f447; // 谷歌地图Google JS API 实现 <template><div class"myMap"><gmp-map :center"center" zoom"15" map-id"ab6b6643adfa1a70"><gmp-advanced-markerv-for"(res, index) in…

python如何不保留小数

1、int() 向下取整&#xff08;内置函数&#xff09; n 3.75 print(int(n)) >>> 3 n 3.25 print(int(n)) >>> 3 2、round() 四舍五入&#xff08;内置函数&#xff09; n 3.75 print(round(n)) >>> 4 n 3.25 print(round(n)) >>> 3 …

Java+前后端分离架构+ MySQL8.0.36产科信息管理系统 产科电子病历系统源码

Java前后端分离架构 MySQL8.0.36产科信息管理系统 产科电子病历系统源码 产科信息管理系统—住院管理 数字化产科住院管理是现代医院管理中的重要组成部分&#xff0c;它利用数字化技术优化住院流程&#xff0c;提升医疗服务质量和效率。以下是对数字化产科住院管理的详细阐述…

NSAT-8000电源模块测试系统提供台式机电源自动化测试方案

在数字化时代&#xff0c;台式机电源的重要性愈发凸显&#xff0c;它不仅是计算机硬件系统的能量心脏&#xff0c;更是保障整个电子生态系统稳定运行的基础。随着人工智能、大数据等技术的飞速发展&#xff0c;计算机系统对电源的性能要求也在不断提高。因此&#xff0c;研究台…

【Linux进程】进程优先级 Linux 2.6内核进程的调度

前言 进程是资源分配的基本单位, 在OS中存在这很多的进程, 那么就必然存在着资源竞争的问题, 操作系统是如何进行资源分配的? 对于多个进程同时运行, 操作系统又是如何调度达到并发呢? 本文将以Linux kernel 2.6为例 , 向大家介绍进程在操作系统中 (OS) 的调度原理; 1. 进程优…

为什么写Python脚本时要加上if __name__ == ‘__main__‘?

目录 一、__name__ 的秘密 二、if __name__ __main__: 的作用 三、代码示例与案例分析 示例一&#xff1a;简单的数学工具模块 示例二&#xff1a;命令行工具 四、实际应用场景 五、进阶应用 1. 插件开发 2. 动态加载模块 3. 交互式与脚本模式切换 六、结论 在Pyth…

阿里云RDS云数据库库表恢复操作

最近数据库中数据被人误删了,记录一下恢复操作方便以后发生时进行恢复. 1.打开控制台&#xff0c;进入云数据库实例. 2.进入实例后 &#xff0c;点击右侧的备份恢复&#xff0c;然后看一下备份时间点&#xff0c;中间这边都是阿里云自动备份的备份集&#xff0c;基本都是7天一备…

优优嗨聚集团:揭秘!轻松化解个人债务危机的实用宝典

在快节奏的现代社会中&#xff0c;个人债务问题日益凸显&#xff0c;成为许多人不得不面对的棘手难题。面对堆积如山的账单和不断增长的利息&#xff0c;我们该如何应对&#xff0c;才能走出债务泥潭&#xff0c;重获财务自由呢&#xff1f;本文将为您揭秘处理个人债务的实用宝…