C++相关概念和易错语法(31)(特殊类的设计、new和delete底层调用分析)

news2024/9/22 23:21:32

特殊类的设计

在实践过程中,我们难免会接触到一些需要实现特定功能的类。像之前提过的unique_ptr就是直接delete拷贝构造和赋值函数。下面会分享一些常见的特殊类的实现

1、防拷贝和防赋值

通过封死拷贝构造和赋值函数来保护对象里面内容不被复制。如果对象里面的内容是指针,对析构次数有严格要求的话(如unique_ptr)就通常采用这种处理方法。

注意拷贝构造和移动拷贝为一体,赋值重载和移动赋值为一体,两两为一组,若其中之一被delete掉了,另一个就算满足自动生成条件(析构、移动、拷贝赋值未手动生成)也没有办法自动生成。

注意封析构不会影响拷贝和赋值的自动生成

所以我们实现防拷贝时,只需要封死拷贝和赋值那一块,而移动拷贝和赋值就不需要我们过多担心了,它们也不会自动生成。因为我们是想要防止拷贝,所以直接delete比起私有化函数更干净利落。

2、只能在栈区创建对象(new和delete底层分析)

如果我们想要只允许在栈区创建对象的话,就要想办法封死堆区和静态区创建的方式。

我先说静态区,静态区无法被封死,因为它的创建、拷贝模式和栈区的几乎没有区别,你可以封死构造,自己写一个构造(栈区值返回,调用构造或移动),但是拦不住静态区也利用构造或移动来拷贝构造。

想要封死堆区创建的方式从操作来讲非常简单,因为我们知道new会去调用operator new,delete会去调用operator delete,所以我们在中间拦截就能实现这个禁用new和delete

但是对new和delete有一定了解的人马上就能找到漏洞,直接跳过中间的拦截,用malloc、calloc、realloc都可以实现在堆区开辟空间

malloc、calloc、realloc是无法拦截的,为什么?以及删除operator new和operator delete为什么会禁止掉new?我们需要深入new和delete的调用规则才能一探究竟。

我们在new和delete混用分析就说过,new主要分为以下阶段:new -> operator new -> malloc,delete阶段分为delete -> operator delete -> free,其中operator new和operator delete都是全局函数,但其实更准确的是在operator new里malloc,在operator delete里面free,operator new之后调用构造函数,而析构函数是在operator delete之前就调用了的。

对于大多数情况,operator new和operator delete都是在全局进行调用的,调用operator new之前就会计算好应该开辟空间的大小

如在这里num接收的就是应当malloc的字节大小num。

关键点来了,C++规定operator new和operator delete可以在类里面自己实现。虽然operator这个标志词让人一下就联想到了重载这个概念,但全局函数和类里面的成员函数首先在作用域上就不相同,其次operator new和operator delete对函数名、参数、返回值都有严格要求,并不能被定义为重载,一般我们可以理解为重定义或者替换。

当在类里面重定义这两个函数时,new这个类或者delete这个类实例化的对象时,当进行到调用operator new或调用operator delete这一步时,都会直接去调用类里面的替换的函数,而不会去调用全局的。

看看下面的代码会如何打印

class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}

	void* operator new(size_t num)
	{
		cout << "void* operator new(size_t num)" << endl;
		return malloc(sizeof(A));
	}

	void operator delete(void* p)
	{
		cout << "void operator delete(void* p)" << endl;
		free(p);
	}	

};

int main()
{
	A* a = new A;
	delete a;

	return 0;
}

结果是

这个结果也可以进一步验证我所说的new和delete的执行步骤,注意malloc(num)中num的具体含义是指开辟的空间大小以及重定义的函数的形式必须完全统一。

如果我们显式删除了operator new和operator delete,就算我们没有显式定义这两个函数,编译器也会解读为我们不希望new这个类的时候调用operator new这个函数,也不会去调用全局的函数,所以在开辟空间这里就卡死了。

这里可以理解为一层特殊处理,但也很符合我们的逻辑,如果你想要调用全局的operator new那就什么都不写,想自己实现就自己写,编译器也会调用(注意格式功能正确),不想自己写也不想调用全局的就直接delete掉这个函数。

还有人知道new[ ]和delete[ ],这两个也是从new和delete中衍生出来的

new[ ] -> operator new[ ] (malloc包含在内),delete[ ]阶段分为delete[ ] -> operator delete[ ](free包含在内)

我们一定要注意operator new[ ]和operator new,operator delete和operator delete[ ]完全独立,没有任何关系

operator new[ ]专门处理开辟数组的情况, 不会去调用operator new。注意size_t num依然意味着要开辟空间的大小,编译器会提前计算好,将真真实实需要开辟的字节数传过来作为num。

在混用分析那里我特地强调了operator new和operator new[ ]的区别,operator new[ ]调用前就会计算要开辟空间的大小(包括多开辟的),会多开辟空间用于存放数组元素个数的信息,返回的时候会将返回的地址二次处理,通过检测new[ ]的元素个数,记录并进行地址的错位返回。只有delete[ ]会在调用operator delete[ ]前进行矫正,将矫正的地址赋给p。

根据上面的规则,结合下面的代码,仔细体会并试图回答为什么不能用operator new[ ]替代new[ ]?为什么不能用operator delete[ ]替代delete[ ]?


class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}

	void* operator new(size_t num)
	{
		cout << "void* operator new(size_t num) : num == " << num << endl;
		return malloc(num);
	}

	void operator delete(void* p)
	{
		cout << "void operator delete(void* p)" << endl;
		free(p);
	}

	void* operator new[](size_t num)
	{
		cout << "void* operator new[](size_t num) : num == " << num << endl;
		return malloc(num);
	}

	void operator delete[](void* p)
	{
		cout << "void operator delete[](void* p)" << endl;
		free(p);
	}

};

int main()
{
	cout << "sizeof(A) == " << sizeof(A) << endl << endl;
	A* a1 = new A[1];
	delete[] a1;	
	
	cout << endl;

	A* a2 = new A;
	delete a2;

	return 0;
}

结果是

这个大小9是编译器自己的处理方式,不用纠结数字如何来的。

由于new和new[ ]、delete和delete[ ]调用的函数不一样,所以当我们删除operator new时,operator new[ ]并不会受到任何影响,依然遵循优先重定义函数其次全局函数的规则,所以我们如果要封死的话,要注意4个函数都delete,不要只写两个

我们只能尽最大努力,控制new、new[ ]、delete、delete[ ]的行为,但malloc、calloc、realloc不支持重定义这套规则,也没办法delete掉这些函数(显式delete的会被认为是成员函数,仍会调用全局的,编译器不会像operator new那样解释)。因此,从某种意义上说,只能在栈区定义的类难以实现,但我们可以在很多层面作出限制,毕竟使用C++一般都不会使用malloc这种C语言语法了,一定程度上起到了规范作用。

3、只能在堆区创建对象

(1)私有化构造函数

只有在堆区创建对象的话,我们要先私有化构造函数,只能以我们的规定的方式来定义函数,这也需要借助静态成员函数来实现。注意我们要分清什么使用该私有化函数,什么时候该delete函数。私有化函数是防止外部调用,但内部可以调用,delete就是完全不可调用。在这里只能私有化,即只能用规定方式创建对象,创建时内部调用构造。

这里我们需要仔细体会,静态成员函数属于整个类而不是某个对象,因此静态成员函数只需要我们指定类域即可,它再调用构造函数就能实现对象的初始化。而如果我们写的是非静态成员函数,那么这就陷入了先有鸡还是先有蛋的问题。非静态成员函数本来就需要先实例化出对象才能调用的,但这个函数的功能又是实例化出对象。

我们上面的实现有个漏洞,即拷贝和移动可以轻松绕过限制,我们在实现特殊类时一定不能忽略拷贝、赋值这两个函数可能带来的漏洞。

因此我们需要针对我们的需求delete拷贝构造或是私有化,对外提供接口

赋值其实在这里是没有必要封的,因为赋值的本质是进行值的覆盖,是对该空间的值的重写。当我们把构造、拷贝函数私有化了之后,我们就只能按照对外的接口来创建空间,显然赋值重载只是将掌管堆区空间的指针进行转移,并不会导致在其他区域开辟了新空间。

(2)私有化析构函数

这是一个很巧妙的办法,利用了非堆区对象出生命周期自动调用析构函数这个特征来禁止调用。

堆区对象的特点就是不主动释放就不析构,最后程序结束时不调用析构直接回收空间。

但上面这个操作明显导致了内存泄漏,因此当我们不用堆区空间,就利用接口来释放空间,这比栈区静态区灵活多了。

很多人这个时候回想:我可以先在栈区构造,用了之后显式析构,这样析构私有化就失效了啊,但事实真的如此吗?

这就陷入了编译时逻辑和运行时逻辑的漏洞,编译器在编译的时候可不会管Destroy()什么意思,只要在栈区实例化出对象,它就会去找析构函数,访问不了就报错,所以虽然运行时逻辑没有问题,但编译都报错了,运行时逻辑还有意义吗?

这里只是特殊类的一些例子,用到的知识已经综合化了,这也可以帮助我们加深语法的印象,拔高我们的思维。

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

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

相关文章

JS 对象深浅拷贝

1. 浅拷贝的原理和实现 自己创建一个新的对象&#xff0c;来接受你要重新复制或引用的对象值。如果对象属性是基本的数据类型&#xff0c;复制的就是基本类型的值给新对象&#xff1b;但如果属性是引用数据类型&#xff0c;复制的就是内存中的地址&#xff0c;如果其中一个对象…

从0开始学杂项 第八期:流量分析(2) 数据提取

Misc 学习&#xff08;八&#xff09; - 流量分析&#xff1a;数据提取 这一期&#xff0c;我们主要写一下如何进行比较繁多的数据的提取。 使用 Tshark 批量提取数据 有时候&#xff0c;我们会需要从多个包中提取数据&#xff0c;然后再进行截取和组合&#xff0c;比如分析…

千云物流 -低代码平台MySQL备份数据

windows备份 全量备份 创建备份目录 需要在安装数据库的服务器上创建备份目录,所有如果要做备份至少需要两倍的硬盘空间, mkdir D:\mysql_backup\full_backup准备备份脚本 创建一个windows批处理文件(例如 full_backup.bat),用来执行全量备份并使用 robocopy 将备份文件…

HTTP 一、基础知识

一、概述 1、概述 HTTP&#xff08;Hyper Text Transfer Protocol&#xff09;&#xff1a; 全称超文本传输协议&#xff0c;是用于从万维网&#xff08;WWW:World Wide Web &#xff09;服务器传输超文本到本地浏览器的传送协议。HTTP 是一种应用层协议&#xff0c;是基于 …

VUE3 使用 <transition> 实现组件切换的过渡效果

由于我想在项目中实现路由组件切换时的平滑过渡效果&#xff0c;以避免页面加载时的突兀感&#xff0c;大致效果如下&#xff1a; 上面的代码是使用的若依的代码&#xff0c;代码具体如下所示&#xff1a; <section class"app-main"><transition name&quo…

HarmonyOS开发移动应用:调用百度翻译开放平台的App Id和密钥

介绍 通过http请求和HarmonyOS自带的加密框架&#xff0c;可以为移动应用实现调用百度翻译API的功能。 开发环境要求 • DevEco Studio版本&#xff1a;DevEco Studio 3.1 Release • HarmonyOS SDK版本&#xff1a;API version 9 工程要求 • API9 • Stage模型 正文 ▍代码…

QT+OSG+osg-earth显示一个球

目录 1、环境配置 2、在QT Creator导入相关的库 3、代码部分 4、运行过程中的问题 5、相关参考 重要衔接&#xff1a;QTOSG显示一个三维模型-CSDN博客 1、环境配置 系统&#xff1a;windows10系统 QT:版本5.15.2 编译器&#xff1a;MSVC2019_64bit 编辑器…

Conda在线/离线迁移虚拟环境

conda简单使用 1.创建环境&#xff1a; conda create -n myenv python3.82.激活环境 conda activate myenv3.退出环境 conda deactivate4.安装包 pip install xxx5.列出所有环境 conda env list conda info --envs6.删除环境 conda remove -n myenv --all离线迁移conda …

GD32F103单片机-概述和工程建立

GD32F103单片机-概述和工程建立 一、GD32F103单片机介绍1.1 GD32F103C8T6引脚1.2 GD32F103C8T6系统架构和启动配置1.3 GD32F103C8T6时钟树 二、GD32F103工程建立 一、GD32F103单片机介绍 GD32F103系列由是由国内公司兆易创新生产的基于Arm Cortex-M3处理器的单片机位数&#x…

陪诊志愿服务正在开展,喜鹊医疗打造国内首家陪诊聚合平台

2024年8月&#xff0c;为了培养一支专业、合格的陪诊志愿服务队伍&#xff0c;为志愿者提供就业帮扶&#xff0c;也满足社会日益增长的健康需求。由喜鹊医疗捐赠专项资金&#xff0c;中国民族卫生协会联合中国志愿基金会共同开展“健康中国行&#xff0c;陪诊惠民工程——陪诊志…

django学习入门系列之第十点《django中数据库操作--创建与删除表》

文章目录 django创建与删除表开始创建表创建指令新增表删除表删除列新增列修改报错提示语言总结 往期回顾 django创建与删除表 删除表 创建表 修改表 操作目录 开始创建表 class text_into(models.Model):name models.CharField(max_length32)password models.CharField…

二手手机回收小程序搭建,小程序功能特点

随着社会生活水平的提高&#xff0c;对手机的更新换代的速度也在逐渐加快&#xff0c;出现了大量的闲置手机&#xff0c;而这也给手机回收市场带来了巨大的发展空间&#xff01; 目前&#xff0c;手机回收市场进入到了发展快速期&#xff0c;吸引了越来越多的企业加入大市场中…

java重点学习-spring

三 spring 3.1 Spring框架中的单例bean是线程安全的吗? 不是线程安全的 Spring框架中有一个Scope注解&#xff0c;默认的值就是singleton&#xff0c;单例的。 因为一般在spring的bean的中都是注入无状态的对象&#xff0c;没有线程安全问题&#xff0c;如果在bean中定义了…

基于纠错码的哈希函数构造方案

一、前言 随着大数据时代的到来&#xff0c;交通数据量急剧增加&#xff0c;由此带来的交通安全问题日益凸显。传统的驾驶人信用管理系统在数据存储和管理上存在着诸多不足之处&#xff0c;例如中心化存储方案无法有效地进行信用存证及数据溯源。区块链技术以其去中心化和不可…

移动硬盘无法访问怎么修复?

移动硬盘是一种方便的存储设备&#xff0c;但有时可能会遇到无法访问的问题。这不仅影响工作效率&#xff0c;还可能导致数据丢失。本文将详细介绍在Windows系统中移动硬盘无法访问怎么修复&#xff0c;帮助您恢复数据和硬盘功能。 移动硬盘无法访问的常见原因 硬盘故障&#…

1998-2023年上市公司金融/信贷/资本资源错配程度数据(含原始数据+计算代码+结果)

1998-2023年上市公司金融/信贷/资本资源错配程度数据&#xff08;含原始数据计算代码结果&#xff09; 1、时间&#xff1a;1998-2023年 2、来源&#xff1a;上市公司年报 3、指标&#xff1a;证券代码、year、应付账款、负债合计、利息支出、行业代码、是否ST或PT、上市日期…

Scott Brinker:Martech中的AI会让买家体验更好还是更糟?这取决于…….

Martech中的AI会让买家体验更好还是更糟&#xff1f; 你怎么知道自己正处于炒作周期的顶峰&#xff1f;当手段大于目的。 Martech专业人士和营销运营领导者正被推动将人工智能应用于营销——将其用于任何事情&#xff01;——相信人工智能的自动化和加速&#xff0c;尤其是生…

通过EasyExcel设置自定义表头及设置特定单元格样式、颜色

前言 在项目开发中&#xff0c;我们会遇到各种文件导出的开发场景&#xff0c;但是这种情况并都不常用&#xff0c;于是本人将自己工作中所用的代码封装成工具类&#xff0c;旨在记录工具类使用方法和技术分享。 实战代码 导出效果&#xff1a; 1、导入依赖 <dependency&g…

开发指南058-JPA多数据源

一般情况下&#xff0c;一个微服务只链接一个数据库&#xff0c;但是不排除有些情况下需要链多个库。链多个库比较复杂&#xff0c;介绍如下&#xff1a; 1、nocas中要配置多数据源 白框内为正常的单数据库情况。下面增加标识&#xff08;可以任意起&#xff0c;这里为eva)&…

Maven入门:自动化构建工具的基本概念与配置

一、什么是Maven 目前无论使用IDEA还是Eclipse等其他IDE&#xff0c;使用里面 ANT 工具帮助我们进行编译&#xff0c;打包运行等工作。Apache基于ANT进行了升级&#xff0c;研发出了全新的自动化构建工具Maven。 Maven使用项目对象模型&#xff08;POM-Project Object Model&…