C++进阶篇10---特殊类设计

一、设计一个类,不能被拷贝

有人可能会觉得,这不是很简单吗,直接把拷贝构造ban掉,不就行了,但事实真的如此吗?

class A
{
public:A(){}A(const A& tmp) = delete;// ...
};int main()
{A a;// A b = a; // 该形式的拷贝被ban了A b;b = a; // 可以通过赋值重载来变相的完成拷贝return 0;
}

所以,正确的做法是将赋值重载和拷贝构造都ban掉,代码如下

class A
{
public:A(){}// C++11 的做法 --- 直接ban掉A(const A& tmp) = delete;A& operator=(const A& tmp) = delete;// ...
private:// C++98 的做法 --- 声明为私有,只声明不实现的原因是类外不能调用,实现了也没用// A(const A& tmp);// A& operator=(const A& tmp);
};

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

在考虑只能在堆上建立对象之前,我们来想想如何禁止在栈上创建对象?而创建对象跟构造函数有关,所以问题变成如何调整构造函数,从而ban掉在栈上创建对象?

其实我们只要将构造函数的访问权限设置为private,那么我们就无法在外部调用构造函数,也就无法在栈上创建对象,代码如下

class A
{
private:A(){} // 将构造函数放在private中,不让外部使用
};

但是,现在我们也不能在堆上创建对象,因为new内部也要调用A的构造函数,如何解决?既然不能在外部创建对象,那么我们只能在类内提供接口,因为类内是允许访问成员函数的,同时,我们也需要让外部在没有对象的情况下能调用到该接口,显然该接口得是静态的成员函数,代码如下

class A
{
public:static A* HeapOnly(){return new A;}
private:A(){} // 将构造函数放在private中,不让外部使用,只能在类内提供接口
};int main()
{A* a = A::HeapOnly();return 0;
}

 但是,现在的代码还是存在问题,结合第一个设计样例的问题,我们很容易发现,当我们在对申请出来的堆上的对象进行拷贝操作时,我们就避开了构造函数,在栈上创建出了对象,如下

int main()
{A* a = A::HeapOnly();A b = *a; // 拷贝构造// A c; c=*a; // 不行,因为赋值的前提是得先有对象return 0;
}

所以,正确的做法是还要将拷贝构造ban掉,代码如下

class A
{
public:static A* HeapOnly(){return new A;}
private:A(){} // 将构造函数放在private中,不让外部使用,只能在类内提供接口// C++11 的做法A(const A& tmp) = delete;// C++98 的做法// A(const A& tmp);
};

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

和设计一个只能在堆上创建对象的类的思路大致相同,我们同样需要将构造函数设为私有,并且对外提供一个静态的成员函数用来返回在栈上创建的对象,代码如下

class A
{
public:static A StackOnly(){return A();}
private:A(){}
};

但是我们如何禁止它在堆上创建对象呢?可能有人会觉得上面这个类,已经无法在堆上创建对象了,因为new无法调用到构造函数,但是,new可以调用拷贝构造呀,你不是能创建栈上的对象吗,我就拿你创建的对象进行拷贝构造,如下

int main()
{A a = A::StackOnly();A* p = new A(a);return 0;
}

那么我们该如何做?将new和delete直接ban掉,从根源上解决问题,一劳永逸,代码如下

class A
{
public:static A StackOnly(){return A();}void* operator new(size_t) = delete;void operator delete(void*) = delete;
private:A(){}
};

四、设置一个类,不能被继承

这个就很简单,直接在给类加上 final 关键字即可(C++11加的),代码如下

class A final
{// ...
};

当然,我们也能通过将该类的构造函数私有化来实现,因为子类对象的创建需要先创建它的父类部分,而显然它如法调用父类的构造函数,故无法被继承,代码如下

// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class A
{
public:static A GetInstance(){return A();}
private:A(){}
};

扩展:结合不能被继承的思路,如果我们不想一个类能被拷贝 / 赋值,除了将该类的赋值重载和拷贝构造ban掉,我们还可以如何做?

我们可以让该类继承一个被ban掉了赋值重载和拷贝构造的基类,这样由于基类成员无法被拷贝和赋值,就会导致它也无法被拷贝和赋值,从而实现了该类也无法被拷贝和赋值的操作

class nocopy
{
public:nocopy(){}nocopy(const nocopy& tmp) = delete;nocopy& operator=(const nocopy& tmp) = delete;
};class A :public nocopy
{//...
};
int main()
{A a;A b = a; // 报错A c; c = a; // 报错return 0;
}

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

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,简化了在复杂环境下的配置管理

1、饿汉模式

提前(main函数启动时)创建好实例对象

如何实现?

1、该对象只能有一个,我们肯定需要将构造函数设置为私有,不让我们能在外面创建对象

2、该对象得是个全局的,且不能在类外创建,它只能是类的静态成员变量,并且类的成员变量一般都设置为私有,所以我们还得给它提供一个静态成员函数,让外界能访问到它

3、根据上面设计的经验,我们还要考虑赋值重载和拷贝构造的问题,这里我们全部ban掉,理由同上 (ps:防止有人整什么奇怪的操作)

如果有没明白的地方,可以看看代码,代码如下

// 假设我们需要一个单例字典
class A
{
public:static A* GetInstance(){return &_inst;}void push(const string& src, const string& dest){_mp[src] = dest;}void print(){for (auto it : _mp){cout << it.first << "  " << it.second << endl;}}A(const A& tmp) = delete;A& operator=(const A& tmp) = delete;private:A(){}
private:map<string, string>_mp;// A _a; // 这种写法是错误的,A类中不能包含A类对象static A _inst; // 这样是可以的,因为静态变量不属于类对象,属于整个类,存放在静态区// 1、静态成员属于类,所以能调用构造函数// 2、静态成员在整个类中只有一份// 3、静态成员能通过 类域 访问,即可以全局访问,只要能找到这个类,当然这里我们需要通过接口间接得到// 完美卡上所有我们需要的条件
};A A::_inst;int main()
{A::GetInstance()->push("left", "左边");A::GetInstance()->push("right", "右边");A::GetInstance()->push("apple", "苹果");A::GetInstance()->push("zxws", "帅");// (doge)A::GetInstance()->print();return 0;
}

优点:实现简单
缺点:
1、可能导致进程启动满,因为它在main函数之前就要被初始化好
2、如果两个单例启动有先后顺序,那么 饿汉模式 无法控制

2、懒汉模式

第一次使用时,才创建,和饿汉模式的代码很相似,如下

class A
{
public:static A* GetInstance(){if (_inst == nullptr){_inst = new A;}return _inst;}void push(const string& src, const string& dest){_mp[src] = dest;}void print(){for (auto it : _mp){cout << it.first << "  " << it.second << endl;}}A(const A& tmp) = delete;A& operator=(const A& tmp) = delete;private:A() {}
private:map<string, string>_mp;static A* _inst;
};A* A::_inst = nullptr;int main()
{A::GetInstance()->push("left", "左边");A::GetInstance()->push("right", "右边");A::GetInstance()->push("apple", "苹果");A::GetInstance()->push("zxws", "帅");// (doge)A::GetInstance()->print();return 0;
}

一般来说,如果没有什么特殊要求,这样写就可以了(进程正常结束会自动释放空间),但是如果我们需要在释放单例之前做一些操作,就需要我们去调用析构函数,同时我们希望它能在程序结束时被自动调用,如何做?代码如下

class A
{
public:static A* GetInstance(){if (_inst == nullptr){_inst = new A;}return _inst;}void push(const string& src, const string& dest){_mp[src] = dest;}void print(){for (auto it : _mp){cout << it.first << "  " << it.second << endl;}}A(const A& tmp) = delete;A& operator=(const A& tmp) = delete;static void DelInstance() // 提供手动释放的接口{if (_inst){// ...delete _inst;_inst = nullptr;}}
private:A() {}
private:map<string, string>_mp;static A* _inst;class gc{public:~gc(){DelInstance();}};static gc _gc; // 用一个类来控制资源,类似智能指针
};A* A::_inst = nullptr;
A::gc A::_gc;

除此之外,懒汉模式在创建和销毁时,还会涉及线程安全问题,即当多个线程同时创建/销毁进程时,会导致空间泄露 / 多次销毁同一段空间 的问题,所以我们需要给它加锁,代码如下

class A
{
public:static A* GetInstance(){if (_inst == nullptr) // 当单例创建完,后续线程在访问时,没必要在去申请锁去判断是否要创建{lock_guard<mutex> lock(_mutex);if (_inst == nullptr){_inst = new A;}}return _inst;}A(const A& tmp) = delete;A& operator=(const A& tmp) = delete;static void DelInstance(){if (_inst) // 当单例释放完,后续线程在释放时,没必要在去申请锁去判断是否要释放{lock_guard<mutex>lock(_mutex);if (_inst){delete _inst;_inst = nullptr;}}}
private:A() {}
private:static A* _inst;class gc{public:~gc(){DelInstance();}};static gc _gc;static mutex _mutex;
};A* A::_inst = nullptr;
A::gc A::_gc;
mutex A::_mutex;

这里说明一点,上面线程安全的单例,只保证在创建和销毁单例时是线程安全的,并不保证在使用该单例的资源时也是线程安全的,需要我们自己去维护。

最简单的懒汉模式

class A
{
public:static A* GetInstance(){// C++11后保证静态变量的创建是线程安全的// 静态变量存放在静态区,只在第一次调用该函数的时候初始化static A inst; return &inst;}A(const A& tmp) = delete;A& operator=(const A& tmp) = delete;
private:A() {}
};

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

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

相关文章

Spring Boot集成itext实现html生成PDF功能

1.itext介绍 iText是著名的开放源码的站点sourceforge一个项目,是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF或rtf的文档,而且可以将XML、Html文件转化为PDF文件 iText 的特点 以下是 iText 库的显着特点 − Interactive − iText 为你提供类(API)来生成…

2.从hello world开始【go】

当然&#xff0c;我们可以从最基本的Hello World程序开始。Hello World程序通常是学习一门新编程语言的第一步&#xff0c;因为它能够帮助你理解基础的程序结构和编译过程 下面是一个Go语言的Hello World程序示例&#xff1a; package mainimport "fmt"func main()…

Leetcode 3101. Count Alternating Subarrays

Leetcode 3101. Count Alternating Subarrays 1. 解题思路2. 代码实现 题目链接&#xff1a;3101. Count Alternating Subarrays 1. 解题思路 这一题我们只需要用贪婪算法对原数组进行切分&#xff0c;使得每一段都是最大的交错子序列&#xff0c;然后&#xff0c;我们要获得…

关于Ansible的模块②

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 接《关于Ansible的模块 ①-CSDN博客》&#xff0c;继续学习和梳理Ansible的常用文件类模块 1. copy模块 从当前机器上复制文件到…

论文复现1:Mobilealoha

abstract:从人类演示中进行的模仿学习在机器人技术中表现出了令人印象深刻的表现。然而,大多数结果都集中在桌面操作上,缺乏一般有用任务所需的移动性和灵活性。在这项工作中,我们开发了一种用于模仿双手且需要全身控制的移动操纵任务的系统。我们首先推出 Mobile ALOHA,这…

从vrrp、bfd、keepalived到openflow多控制器--理论篇

vrrp 在一个网络中&#xff0c;通常会使用vrrp技术来实现网关的高可用。 vrrp&#xff0c;即Virtual Router Redundancy Protocol&#xff0c;虚拟路由冗余协议。 应用场景 典型的如下面这个例子&#xff1a; 当Router故障后&#xff0c;将会导致HostA-C都无法连接外部的I…

自动驾驶杂谈

在2024年的今天&#xff0c;自动驾驶技术已经迈向了一个崭新的阶段&#xff0c;日趋成熟与先进。昨日&#xff0c;我有幸亲眼目睹了自动驾驶车辆在道路上自如行驶的场景。然而&#xff0c;在市区拥堵的路段中&#xff0c;自动驾驶车辆显得有些力不从心&#xff0c;它们时而疾驰…

Spring Boot集成JPA快速入门demo

1.JPA介绍 JPA (Java Persistence API) 是 Sun 官方提出的 Java 持久化规范。它为 Java 开发人员提供了一种对象/关联映射工具来管理 Java 应用中的关系数据。他的出现主要是为了简化现有的持久化开发工作和整合 ORM 技术&#xff0c;结束现在 Hibernate&#xff0c;TopLink&am…

C#调用FreeSpire.Office读取word数据的基本用法

FreeSpire.Office是Spire.Office的免费版本&#xff0c;后者支持全面、复杂的office文件操作功能&#xff0c;包括文件格式转换、文档操作、文档打印等&#xff0c;详细介绍见下图及参考文献1。本文学习FreeSpire.Office的基本用法并用其获取word文档的基本信息。   新建Win…

VTK中polydata的属性数据结构表示和用法

vtk中通过vtkDataArray进行数据的存储&#xff0c;通过vtkDataObject进行可视化数据的表达&#xff0c;在vtkDataObject内部有一个vtkFieldData的实例&#xff0c;负责对数据的表达&#xff1a; ​​​​​​​ vtkFieldData存储数据的属性数据&#xff0c;该数据是对拓…

《福建教育》期刊简介及投稿要求

《福建教育》期刊简介及投稿要求 《福建教育》国内外公开发行的学术期刊&#xff0c;目前出版文献量达19187篇&#xff1b;总下载次数&#xff1a; 1361672次&#xff1b;总被引次数&#xff1a; 8709次 《福建教育》是福建省教育厅主管的唯一一份主流教育专业期刊&#xff0…

【嵌入式智能产品开发实战】(十二)—— 政安晨:通过ARM-Linux掌握基本技能【C语言程序的安装运行】

目录 程序的安装 程序安装的本质 在Linux下制作软件安装包 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 嵌入式智能产品开发实战 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xf…

再探Java为面试赋能(一)Java基础知识(一)变量初始化顺序、构造方法、clone方法

文章目录 第1章 Java基础知识1.1 变量的初始化顺序1.2 构造方法1.3 clone()方法1.3.1 按值传递和按引用传递1.3.2 浅拷贝&#xff08;Shallow Clone&#xff09;1.3.3 深拷贝&#xff08;Deep Clone&#xff09; 第1章 Java基础知识 1.1 变量的初始化顺序 在Java语言中&#…

MybatisPlus速成

MybatisPlus快速入门 快速入门入门案例常见注解常见配置 核心功能条件构造器自定义SQLService接口 扩展功能代码生成静态工具逻辑删除枚举处理器JSON处理器 插件功能分页插件通用分页实体 参考文档 mybatis-plus参考文档 全部资料链接 讲义 快速入门 入门案例 <dependency…

骑行不将就,坐垫要讲究!跟维乐来一场骑美合一的美学旅行~

想象一下&#xff0c;你胯下的坐垫不再是冷冰冰的硬疙瘩&#xff0c;而是化身为“骑行界的舒适艺术家”。美学坐垫宛如马鞍上的微型沙发&#xff0c;采用美学与人体工学的跨界联姻&#xff0c;不仅赏心悦目&#xff0c;更能温柔拥抱你的臀部。它那精妙的曲线设计&#xff0c;仿…

Pandas基本操作

import pandas as pd import numpy as np#读入csv文件 book_df pd.read_csv("./doubantushu2.csv",sep,,headerNone,names[bookname,writer,publication,year,price,value])#inplace "",表明是否对原数据库进行修改&#xff0c;默认为False&#xff08;不…

AI大模型在金融行业的应用场景和落地路径

作者&#xff1a;林建明 来源&#xff1a;IT阅读排行榜 本文摘编自《AIGC重塑金融&#xff1a;AI大模型驱动的金融变革与实践》&#xff0c;机械工业出版社出版这是最好的时代&#xff0c;也是最坏的时代。尽管大模型技术在金融领域具有巨大的应用潜力&#xff0c;但其应用也面…

minor 通过nginx代理 配置 OK

#以下勿动 location /fileStorage/upload/ { proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_he…

EXCEL VBA限制工作数据批号或者自定义规则完整

EXCEL VBA限制工作数据批号或者自定义规则完整 Private Sub Worksheet_Change(ByVal Target As Range)Dim nRow%, Arr(), cMc$, cPc$, cTxt$, nSum!If Target.Row 1 Or Target.Column <> 4 Then Exit SubIf Target.CountLarge > 1 Then Exit SubcMc Target.Offset(0…

再说机器学习

之前我们讨论过机器学习&#xff0c;那是在大厂AI课笔记里面。 今天我们再来说说机器学习。 机器学习概念 机器学习是人工智能的一个子领域&#xff0c;它的核心是让计算机从数据中学习&#xff0c;从而能够自动地改进其性能&#xff0c;在没有明确编程的情况下能够预测新数…