C/C++相关概念和易错知识点(56)(特殊类的设计、new和delete底层调用分析)

工业视觉开发者的福音:用C#玩转VisionMaster算子(非方案版完整教程) 本文为工业视觉开发者提供了一份完整的C#与VisionMaster算子二次开发教程,详细介绍了环境配置、算子调用原理、性能优化技巧及实战案例。通过模板匹配和二维码识别等实例,展示了如何高效集成VisionMaster算子到现有框架中,提升工业自动化视觉检测系统的灵活性和性能。 阅读详情

特殊类的设计

在实践过程中,我们难免会接触到一些需要实现特定功能的类。像之前提过的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()什么意思,只要在栈区实例化出对象,它就会去找析构函数,访问不了就报错,所以虽然运行时逻辑没有问题,但编译都报错了,运行时逻辑还有意义吗?

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

Python 爬虫实战:爬取 GitHub 热门项目(星数 / 语言 / 作者) 本文系统介绍了使用Python爬虫技术获取GitHub热门项目数据的完整方案。通过分析GitHub Trending页面和官方API两种数据源,详细讲解了HTML解析、JSON数据处理、反爬应对策略等关键技术。采用requests、BeautifulSoup、pandas等工具链,实现了项目信息(名称、星数、语言等)的结构化爬取,并配套数据清洗、统计分析及可视化功能。特别强调遵守GitHub API使用规范,包括Token认证、请求频率控制等合规要求。该方案为开发者提供了一种高效、合法的数据采集方法,适用于 阅读详情

相关推荐

FPGA时钟设计陷阱:为什么你的MRCC引脚总触发Place 30-675错误?

本文深入解析FPGA开发中常见的Place 30-675错误,揭示MRCC引脚与时钟资源分配的关键问题。通过对比MRCC与普通IO引脚特性,分析错误根源,并提供时钟约束优化、IP核重配置和手动布局三套解决方案,帮助工程师规避时钟设计陷阱,提升FPGA系统稳定性。

potato的博客 693

C/C++——new和delete的实现原理(详解)

C/C++内存管理1.C/C++内存分布2.C语言中动态内存管理方式2.1malloc/calloc/realloc/free区别3.C++中动态内存管理(new和delete)3.1new/delete操作内置类型3.2new/delete操作自定义类型4.operator new和operator delete函数4.1operator new和operator delete 底层原理4.2operaor new和operator delete的类专属重载5.new和delete的实现原理6.定位new

今天你AC了吗 8096

足球赛事数据API:开发者生存指南(附真实踩坑记录)

作为一个既爱看球又爱写代码的老宅男,我这些年用过的足球API比看过的比赛还多。从最初被各种API文档折磨到怀疑人生,到现在能闭着眼睛调接口,中间的血泪史够写本书了。今天就把这些经验(和教训)分享给各位同好,特别是那些正在为"老板要做个足球App"而秃头的同行们。文档清晰免费层可用响应格式规范API-Sports免费额度大方支持GraphQL有中文文档(难得!Sportmonks数据全面有Webhook支持按需付费。

Happy0970的博客 2191

内存管理--《Hello C++ Wrold!》(8)--(C/C++)--深入剖析new和delete的使用和底层实现

本文系统介绍了C/C++内存管理的核心知识。首先剖析了程序内存分布,包括栈区、堆区、静态区和常量区的功能特性。重点对比了new/delete与malloc/free的六大关键区别,包括初始化方式、失败处理和自定义类型处理等差异。深入解析了new/delete底层通过operator new/delete调用malloc/free的机制,并介绍了定位new表达式在内存池中的特殊应用。最后讨论了内存泄漏问题及其解决方案。文章通过典型例题和面试考点,帮助读者掌握内存管理的实践技巧和问题排查方法,为学习智能指针等进

刃神太酷啦的博客 1万+

手把手教你用Ubuntu 22.04的附加驱动工具安装NVIDIA显卡驱动(含常见错误修复)

本文详细介绍了在Ubuntu 22.04系统上,如何通过图形化的“附加驱动”工具安全、便捷地安装NVIDIA显卡驱动。内容涵盖从准备工作、禁用默认的nouveau驱动、图形界面安装实操,到安装后的验证(如使用nvidia-smi命令),并提供了安装失败黑屏、亮度调节失效等常见问题的深度解决方案,帮助用户彻底搞定驱动安装难题。

weixin_29248653的博客 602

用Python进行GEO数据挖掘(学习笔记四):富集分析

该笔记详细解释了富集分析的工作原理,并简要地利用gseapy在python中进行富集分析的操作。

m0_64111502的博客 5649

C/C++相关概念和易错知识点(29)(变量的存储、new和delete混用分析)

我们发现如果这个时候直接去调用operator delete[]、operator delete、delete、free就会导致释放的内存起始位置发生了位移,这就会导致报错,而delete[]就会先从前4个字节开始,先读取个数,然后。注意数组是开辟了空间的,会发生复制操作,就算是常量字符串储存在代码段,也要拷贝过来。如果类没有显式实现析构函数,那么就意味着不会多开辟那4个字节,也就不会报错,这个时候混用不会导致程序崩溃。当我们创建类的数组时,使用new[],因为使用它可以在开辟空间的时候。

SGlow0708的博客 1047

你的小程序地图功能还只是“摆设”吗?支付宝地图API的深度玩法都在这里了!

🏆本文收录于《滚雪球学支付宝小程序开发》专栏,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!

**My Coding Family** 1820

【C++初阶】七、内存管理(C/C++内存分布、C++内存管理方式、operator new / delete 函数、定位new表达式)

C++内存管理:C/C++内存分布 ;C++内存管理方式 ;operator new 和 operator delete 函数 ;定位new表达式(placement-new)(了解)

正在努力学C呀!(要学好久呢......) 6790

23种设计模式-享元(Flyweight)设计模式

软考中级-软件设计师 享元设计模式

片言可决讼,半纸能缚龙 3223

12027.linux flash驱动之IS25LP01G/IS25WWP01G

一页:512字节 --> 扇区 4096字节 --> 块 32k,64k, 层层递进关系。

写给自己的笔记 2289

【C++升级之路】第五篇:C/C++内存管理(new和delete的实现原理)

C/C++内存分布,C/C++动态内存管理方法,C++动态内存管理方法底层函数operator new 和operator delete函数实现,new和delete的实现原理,定位new的概念和基础应用,内存管理常见面试题。

Captain_ldx的博客 2672

智驾车载以太网(18)-switch之报文转发和vlan处理流程

本文详细解析了交换机中报文处理和转发流程:1)通过目标MAC查找转发端口,源MAC创建ARL条目;2)VLAN标签处理包括tagged/untagged帧判断、默认VID标记等5种处理方式;3)完整转发流程包含CRC校验、IQ封装、VLAN和ARL表查询、端口向量计算及优先级队列发送。文中还涉及TCAM修改VLAN、802.1q多种模式处理等细节,省略了流量控制等次要流程。该过程实现了基于MAC和VLAN的高效二层交换。

mitugaoyang1993的博客 43

【C++那些事儿】C++内存管理 | new和delete的底层原理 | operator new与operator delete函数 | 你听说过定位new吗?| 内存泄露

1、注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。 2.、在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会 3、实际上operator new 是对 malloc 的封装,operator delete 是对 free 的封装(他们的用法是一样的)。 4、定位new表达式是在已分配的原始内存空间中(显示)调用构造函数初始化一个对象。

深入解析C/C++编程技巧,带你探索高效开发之路 3070

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

【C/C++】跟我一起学_揭秘C++中new/delete与malloc/free的底层关系

new/delete 与 malloc/free 的关系

YZJincsdn的博客 1166

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

c++ new delete new[] delete[] 底层实现

关于delete和delete[]的相关实现。

YueLing's Blog 8227

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

C++new和delete实现原理

new操作 delete操作 new[]操作 delete[]操作

passion_wu128的专栏 2万+

【C++】内存管理 —— new和delete底层实现原理

真的不点进来看看吗?【C++】内存管理,深度刨析底层内存的模样,激情创作一天搞定,每日积攒小幸福

qq_42996461的博客 760

《C++初阶之内存管理》【内存分布 + operator new/delete + 定位new】

这篇文章主要讲解了C/C++程序的内存分布以及C++中的operator new/delete和定位new。文章通过代码示例详细分析了各变量在内存中的分布位置,并介绍了C++特有的operator new/delete和定位new的功能与用法,这些都是C++内存管理的核心知识。

序属秋秋秋的博客 2036

c/c++内存管理(详解) + new与delete的用法及底层

c/c++内存,new与delete的底层原理

2201_75964502的博客 1157

C/C++ 内存管理深度解析:从内存分布到实践应用(malloc和new,free和delete的对比与使用,定位 new )

本文深入探讨了C/C++内存管理的核心概念与最佳实践。首先,文章分析了C/C++程序的内存分布,包括内核空间、栈区、堆区、数据段和代码段,解释了各区域的功能与管理方式。接着,详细解析了C语言中的动态内存管理函数(malloc、calloc、realloc、free)及其使用场景,并对比了C++中的new/delete操作符,强调了其在自定义类型中的构造与析构功能。文章还介绍了定位new表达式及其在内存池技术中的应用。最后,总结了malloc/free与new/delete的深度对比,并提供了内存管理的最佳实

2501_90200491的博客 2486

【C++从入门到放弃】C/C++内存管理(new和delete的用法详解)

在学习C/C++内存管理之前,我们先看一下下面的代码与相关问题。free(ptr1);free(ptr3);选择题:选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)globalVar在哪里?____ staticGlobalVar在哪里?____staticVar在哪里?____ localVar在哪里?____num1 在哪里?____char2在哪里?____ *char2在哪里?___pChar3在哪里?

@情话 1185

C/C++内存管理:new/delete,operator new/delete用法详解

答:(从左往右,从上往下顺序)前3个选C:因为全局变量,静态变量或静态全局变量生命周期都是全局,都属于静态区。静态的全局的在静态区;不可修改的常量,编译好的指令在常量区。const都可以修饰局部,全局,静态变量,不改变其空间位置,只是使其不可修改。常变量也是在栈上的4,5选A:局部变量和数组都是在栈上。6,7选A:6是数组存在栈上没问题,7是数组指向的内容,他是把常量字符串的内容拷贝到数组中,数组指向的内容是在栈上.................................

zhang_si_hang的博客 1577

C++初阶:C/C++内存管理、new与delete详解

class Apublic://构造函数A(int a=0):_a(a){}//析构函数~A()cout << "调用了~A()" << endl;int main()//动态申请一个A类型的空间,并且调用构造函数初始化为1//动态申请3个A类型的空间A a1;A a2;A a3;//可以直接写成以下两种//匿名对象//再少点//隐式类型转换,有单形参的构造函数支持delete pa1;return 0;

weixin_70067429的博客 1013

C++中的new和delete

详细介绍C++中的new和delete操作符

talk less, code more 1140

c/c++笔试题——C++中 new/delete 概念和用法总结

      学过C++的程序员都知道,new/delete是其管理内存的主要方法。与C语言相比,C++支持面向对象技术,因此在内存管理方面有所区别。      在C++中,数据对象(基本类型和复合类型)的创建包括两个部分:一是给数据对象分配足够的内存,二是内存进行数据的初始化。而C中,只有第一个分配内存的作用。并且,不仅仅是类,其它基本类型,如int型等,也是如此。例如:      cha

hsujouchen的专栏 4339
上一篇: C/C++相关概念和易错知识点(55)(异常、智能指针)
下一篇: C/C++相关概念和易错知识点(57)(单例模式、类型转换)
-SGlow-
博客等级 码龄3年 1513粉丝 118原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值