《认清C++语言》---堆对象

C++语法分析器tree-sitter-cpp:轻量级代码解析与编辑器集成实战 语法分析器是编译原理中的核心组件,负责将源代码字符流转换为结构化的语法树,为代码理解、高亮和静态分析提供基础。其工作原理基于形式语言与自动机理论,通过词法分析和语法分析构建程序的结构化表示。在工程实践中,语法分析器的技术价值在于实现代码的自动化处理,如智能补全、错误检测和重构工具。应用场景广泛覆盖IDE开发、代码质量检查和文档生成等领域。本文聚焦于tree-sitter-cpp这一轻量级C++语法分析器,它采用增量解析和声明式语法定义,高效处理C++的模板和预处理器等复杂特性,并支持查询语言进行精准的节点提 阅读详情

强制在堆中建立对象:

有时候,我们要求某种类型的对象能够自我销毁,也就是能够“delelte this”,因此,我们需要将此类型的对象分配在堆上。由于非堆对象在定义它的地方被自动构造,在生存期结束时被自动释放,因此我们只要禁止使用隐式的构造函数和析构函数就可以限制对象在堆上分配内存。

 

最直接的方法是将构造函数和析构函数都声明为private,但这样副作用太大。因此,最好将析构函数置为private,构造函数置为public(这样可以实现限制是因为C++的异常处理体系要求所有在栈中的对象的析构函数必须申明为public的!)。同时引入一个专用的伪析构函数,用来访问真正的析构函数。

建立对象如下:

class ASCENumber

{

public:

         ASCENumber();

         ASCENumber(int iValue);

         ASCENumber(double dValue);

         ASCENumber(const ASCENumber& other);

         //伪析构函数,const成员函数

         void destroy() const {delete this;}

         ...

private:

         ~ASCENumber();

};

调用如下:

ASCENumber num;        //错误,虽然在这里是合法的,但是当num的析构函数被隐式调用时,会出错!

ASCENumber *pnum = new ASCENumber;      //正确

...

delete pnum; //错误,试图调用private析构函数

pnum->destroy();          //正确

 

上面说到将全部构造函数都声明为private的副作用体现在:一个类经常有很多构造函数,我们必须记住把它们声明为private,否则,编译器会默认生成它们,如拷贝构造函数、缺省构造函数,而且是public的。

通过限制访问一个类的析构函数或构造函数来阻止建立非堆对象,还有一个缺点是这种方法同时也禁止了类的继承。

class ASCENumber {...};         //声明析构函数或构造函数为private

class LittleAsceNumber :

         public ASCENumber {...};     //错误,析构函数或构造函数不能编译

 

class Asset

{

private:

         ASCENumber value;

         ...                        //错误,析构函数或构造函数不能编译

};

为了解决继承问题,我们可以把ASCENumber的析构函数声明为protected,同时保持构造函数为public。而需要包含ASCENumber对象的类可以修改为包含指向ASCENumber的指针:

class ASCENumber {...};                  //声明析构函数为protected

class LittleAsceNumber :

         public ASCENumber {...};     //正确,派生类能够访问protected成员

 

class Asset

{

public:

         Asset(int iValue);

         ~Asset();

         ...

private:

         ASCENumber *value;             //包含指针,而不是对象

};

 

Asset::Asset(int iValue) : value(new ASCENumber(iValue))   //正确

{

         ...

}

Asset::~Asset()

{

         value->destroy(); //正确

}

 

判断一个对象是否在堆中:

上面的ASCENumber的构造函数不能判断出它是否作为堆对象的基类而被调用,即无法检测下面两种环境的区别:

ASCENumber *pasce = new ASCENumber ;      //在堆中

ASCENumber asce;         //不在堆中

方案一:很多系统上程序的地址空间被作为线性地址管理,程序的栈从地址空间的顶部向下扩展,堆则从底部向上扩展。利用这个事实,我们可以使用下面的函数来判断某个特定的地址是否在堆中:

//不可移植,且没考虑静态变量!!来判断一个地址是否在堆中

bool OnHeap(const void *address)

{

         char onTheStack;            //局部栈变量

         return address < &onTheStack;

}

上面函数最根本的问题是对象被分配在三个不同地方,而不是两个。除了堆和栈外,还有静态存储区。静态对象是那些在程序运行时仅能初始化一次的对象,包括static对象、全局和命名空间里的对象,它们都放在静态存储区中。且这个区域是根据系统而定的,在很多栈和堆相向扩展的系统里,静态存储区位于堆的底端。因此,当考虑静态对象时,上面的OnHeap函数就不能辨别堆对象和静态对象了!!:

void allocateObjects()

{

         char *pc = new char;    //堆对象,OnHeap(pc)将返回true

         char c;               //栈对象,OnHeap(&c)将返回false

         static char sc;  //静态对象,OnHeap(&sc)将返回true

}

 

方案二:“判断是否能够删除一个指针”比“判断一个指针指向的事物是否在堆上”要简单。这里为了能够不污染全局命名空间,且没有额外的开销,没有正确性问题,我们使用抽象混合基类来给派生类提供判断指针指向的内存是否由operator new分配的能力,该类的声明如下:

class ASCEHeapTracked         //混合类,跟踪从operator new返回的ptr

{

public:

         class MissingAddress{}; //异常类

         virtual ~ASCEHeapTracked() = 0;     //虚析构函数,是ASCEHeapTracked成为抽象类

         static void *operator new(size_t size);

         static void operator delete(void *ptr);

         bool IsOnHeap() const;

private:

         typedef const void* RawAddress;

         static std::list<RawAddress> addresses;

};

这个类使用list数据结构来跟踪从operator new返回的所有指针。operator new函数分配内存并将地址加入到list中;operator delete用来释放内存并从list中移去地址元素;IsOnHeap函数判断一个对象的地址是否在list中。ASCEHeapTracked的实现如下:

//静态成员变量的定义(必须)

std::list<RawAddress> ASCEHeapTracked::addresses;

//析构函数是纯虚函数,使得该类变成抽象类

//析构函数必须被定义

ASCEHeapTracked::~ASCEHeapTracked(){}

 

void *ASCEHeapTracked::operator new(size_t size)

{

         void *ascePtr = ::operator new(size);         //全局operator new,获得内存

         addresses.push_front(ascePtr);    //把地址放到list的前端

         return ascePtr;

}

 

void ASCEHeapTracked::operator delete(void *ptr)

{

         //得到iterator

         std::list<RawAddress>::iterator it =

                   find(addresses.begin(), addresses.end(), ptr);

         if(it != addresses.end())        //如果发现一个元素

         {

                   addresses.erase(it);        //删除它

                   ::operator delete(ptr);  //释放内存

         }

         else  //ptr不是operator new分配的,所以抛出一个异常

         {

                   throw MissingAddress();      

         }

}

 

bool ASCEHeapTracked::IsOnHeap() const

{

         //得到一个指针,指向*this占据的内存空间的起始处

//也就是指向当前对象其实地址的指针(由ASCEHeapTracked::operator new返回的)

         const void *rawAddress = dynamic_cast<const void*>(this);

         //在operator new返回的地址list中查到指针

         list<RawAddress>::iterator it =

                   find(addresses.begin(), addresses.end(), rawAddress);

         return it != addresses.end();         //返回it是否被找到

}

因此,以后想要在类中加入跟踪堆中指针的功能,只需要继承ASCEHeapTracked就行:

class Asset : public ASCEHeapTracked

{

private:

         ASCENumber value;

         ...

};

//我们可以这样查询Asset*指针

void checkAsset(const Asset *pa)

{

         if(pa->IsOnHeap())

         {

                   //pa建立在堆上的对象

         }

         else

         {

                   //pa不是建立在堆上的

         }

}

 

禁止堆分配:

1)通常,指明一些特定类的对象不应该被分配到堆(heap)上是个好主意。通常这是为了确保该对象的析构函数一定会得到调用。因为堆分配的对象必须被显示地销毁,忘记了的话就造成内存泄漏了!通常对象的建立有这样三种情况:对象被直接实例化;对象作为派生类的基类被实例化;对象被嵌入到其他对象中(组合composite)。

指明对象不应该被分配到堆上的方式之一,是将其堆内存分配定义为不合法的。直接实例化总是调用new来建立对象,但是我们不能重载new操作符(它内置与C++语言中),但是我们能够重载operator new函数来达到目的:

class NoHeap

{

public:

//.....

protected:

void *operator new(size_t) { return 0;}; //注意,这里是operator new而不是new operator,

//因为只有operator new能够被重载

void operator delete(void*){};

};

任何在堆上分配一个NoHeap对象的操作,都会导致编译期错误:

NoHeap *nh = new NoHeap;     //Err!NoHeap::operator new是受保护的

//..........

delete nh;                          //Err!NoHeap::operator delete是受保护的

之所以将operator new和operator delete声明为protected,是因为它们可能会被派生类的构造函数和析构函数隐式地调用。如果NoHeap不打算用作基类,则这两个函数也可以声明为private。

 

有趣的是,当把operator new声明为private时:

(1) 而且NoHeap用作基类时,由于operator new和operator delete是自动继承的,所以派生类建立堆对象时会报错,除非派生类自己改写operator new和operator delete,并声明为public;

(2) 不会对包含NoHeap成员对象的对象的分配产生任何影响:

class Asset

{

public:

         Asset(int iValue);

         ...

private:

         NoHeap value;

};

 

Asset *pa = new Asset(10);  //正确,调用Asset::operator new或::operator new

                                     //而不是ºNoHeap::operator new

 

2)注意还要防止在堆上分配NoHeap对象的数组。此时,只要将array new和array delete声明为private

且不予定义即可:

class NoHeap

{

public:

//...........

protected:

void *operator new(size_t) {return 0;}

void operator delete(void*){}

private:

void *operator new[] (size_t);

void operator delete[] (void*);

};

 

 

 

 

 

Ubuntu关闭更新提醒的三层控制方法:从GUI到APT配置 Linux系统更新机制本质上是包管理器(APT)驱动的自动化运维流程,其核心原理在于分离‘元数据刷新’、‘安全补丁安装’和‘用户通知’三个职能。这种分层设计既保障了基础安全性,又为系统管理员提供了策略定制空间。技术价值体现在提升生产环境稳定性、规避未经验证的内核/驱动升级风险,并支持ROS、Blender渲染、工业控制等对时序敏感的应用场景。本文聚焦Ubuntu系统入门教程中高频需求——科学关闭软件更新提醒,覆盖Ubuntu 20.04及LTS版本的GUI设置、systemd服务禁用与APT配置修改三大实操 阅读详情

相关推荐

数字设计实验:RISC-V指令单周期CPU

本文旨在介绍RISC-V指令单周期CPU的设计思路和仿真实现(含代码)

2401_83740084的博客 2713

【C++】堆对象

一、什么是堆对象 堆对象是指程序在运行过程中可以根据需要随时可以建立或删除的对象 又称为动态对象,分为New 和Delete 学过数据结构的同学都知道,malloc、free函数 那么 New就相当于malloc Delete就相当于free 二、堆对象的使用 (1)New的使用 new () (2)Delete的使用 delete 三、堆对象的一个实例 代码块: #

皓皓松的博客 1830

探索Vortex开源GPGPU:RISC-V SIMT架构(4-1),Execute SFU(1)

本文详细分析了Vortex RISC-V内核6级流水线中Execute阶段的SFU(特殊功能单元)模块。重点介绍了SFU的数据流结构,包含dispatch_unit、pe_switch、csr_unit、wctl_unit和gather_unit五个子模块。文章深入解析了warp控制指令wspawn和tmc的实现机制:wspawn用于激活指定数量的warp并设置新PC值,tmc则用于控制线程掩码和warp状态。通过C代码和汇编指令的对照分析,阐明了这些指令在并行计算中的关键作用,包括线程激活、状态切换和执行

weixin_55313207的博客 1363

6.4 子对象和堆对象

int b;在上述例子中,类B中的成员a就是子对象,它是A类的对象作为B类的成员。

tang7mj的博客 649

matlab 用m_map画地形水深图

clear,clc %投影 m_proj('albers equal-area','lat',[5 43],'long',[105 125],'rect','on'); %加载水深文件 [CS,CH]=m_etopo2('contourf',[-7000:500:3000],'edgecolor','none'); %绘制高分辨率陆地和海岸线 m_gshhs_h('patch',[0.1 0.9 ...

qq_44913577的博客 9511

人工智能训练师技能等级认定四级技能考核试卷-样题1

本文为人工智能训练师四级技能考核试卷,包含六个实操考核项目,总分100分,考试时间150分钟。试卷涵盖数据处理、模型训练准备及系统维护等核心技能:1)垃圾分类数据集规范与处理(30分),要求评估数据格式并编写Python处理代码;2)学生成绩数据集分析(20分),涉及数据预处理、可视化及相关性分析;3)数据标注审核(10分),重点审核标注准确性;4)智能数据服务系统维护与优化(40分),包括安全帽数据集的项目创建、数据处理及平台协作。考核强调Python编程、数据处理平台操作及AI工作流程的实践能力。

weixin_42221934的博客 440

堆对象

堆,又叫自由存储区,它是在程序执行的过程中动态分配的,所以它最大的特性就是动态性。在C++中,所有堆对象的创建和销毁都要由程序员负责,所以,如果处理不好,就会发生内存问题。   如果分配了堆对象,却忘记了释放,就会产生内存泄漏; 而如果已释放了对象,却没有将相应的指针置为NULL,该指针就是所谓的“悬挂指针”,再度使用此指针时,就会出现非法访问,严重时就导致程序崩溃。   那么,C++中是...

867

某验SVG图标验证码识别思路(非训练)

本文提出了一种无需训练的某验SVG图标验证码识别方法。通过分析SVG文件结构,使用正则表达式提取静态图标数据,并借助CairoSVG库转换为PNG格式。随后采用坐标定位方式裁剪出12个固定位置的图标,最后利用ResNet50提取特征,通过余弦相似度匹配目标图标。该方法避免了训练目标检测模型,实现了高效准确的验证码识别。关键步骤包括SVG文件预处理、图标定位裁剪和基于预训练模型的相似度比对。

九月镇灵将的博客 419

C++复杂对象之堆对象

这学期增加的数据结构的课,感觉冯老师还是蛮好的,总是在给我们补一些我们学C++时遗留的东西,她在编写一道例题时,用了运算符new和运算符delete,我这才想起了接触过这个东西,但是忘了,最近的做题的过程中也遇到了这种东西,就是动态的创建一个变量或对象,今天就来说堆对象。        堆对象        在程序运行中,根据需要随时创建的对象称为堆对象。堆对象是被存放在内存堆区的一种变量,它

life is wonderful 1719

肖特基二极管的反向恢复过程

本博客简述肖特基二极管反向恢复过程。

记得诚 1万+

19、多项式基算术:原理与运算详解

本文深入探讨了有限域 $GF(2^m)$ 中的多项式基算术,详细介绍了加法、减法、乘法和平方运算的原理与实现方法。重点分析了多项式基表示形式、模约简技术以及乘法优化策略,如高基数乘法、并行与顺序乘法器设计,并提到了Karatsuba-Ofman算法在减少乘法次数方面的优势。此外,文章还讨论了多项式基算术在密码学、编码理论等领域的应用前景,并展望了未来在高效算法设计与硬件加速方面的研究方向。

yellow的专栏 150

C++ 中堆对象的使用总结

C++ 中堆对象的使用总结

编程猿来如此 1029

YOLOv11在STM32H7上的MCU部署实战:基于CMSIS-NN的高效推理实现

嵌入式AI与MCU部署技术解析 本文系统介绍了基于STM32H7微控制器的YOLOv11模型部署方案。主要内容包括: 边缘计算背景下MCU的AI部署优势与挑战,重点分析了STM32H7平台特性 YOLOv11模型的结构优化,包括E-ELAN骨干网络和Meta-ACON激活函数等改进 CMSIS-NN加速库的架构原理,在STM32H743上可实现5.4倍卷积加速 完整的开发环境搭建流程,涵盖硬件选型、工具链配置和模型转换 模型优化关键技术,包括int8量化、剪枝和内存占用优化等 实验表明,该方案在保持模型精度

本博客聚焦 YOLOv11 全流程落地,涵盖架构优化、数据集处理、训练技巧与多场景部署,兼及国产数据库、Java 开发与 AI 模型应用,内容兼顾理论与工程实践,为开发者提供系统干货,助力高效提升技术能力。 853

《认清C++语言》---理解临时对象

<br />对于临时变量经常会有一个误解,就是将仅仅需要一小段时间的变量称为临时变量:<br />template<typename T><br />void swap(T& obj1, T& obj2)<br />{<br />         T temp = obj1; //误解为temp是临时变量,其实正确地说temp是一个局部变量<br />         obj1 = obj2;<br />         obj2 = temp;<br />}<br /> <br />在C++中真正的临时变

ASCE1885 1825

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

《认清C++语言》---new and delete

1)  new操作符(new operator)和operator new函数的区别: 下面的代码:string *ps = new string("Memory Management");使用的new是new操作符,这个操作符就像sizeof一样是内置于语言本身的。它所完成的功能分成两部分:首先分配足够的内存以便容纳所请求类型的对象(如上面的string对象);其次它会调用构造函

ASCE1885 1623

ssm框架搭建

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

《认清C++语言》---谈谈const

const类型修饰符可以:1)  用来说明符号常量和常数组(必须同时进行初始化,以后不能再更新)C++中,用关键字const修饰的标识符称为符号常量,或称为const变量。常量在使用前必须先定义同时初始化 ,例如:const double pi = 3.1415926或double const pi = 3.1415926; 通常情况下const同预处理器#de

ASCE1885 1777

认清C++语言之《函数指针》

函数指针:1)  可以声明一个指向特定类型函数的指针:void (*fp)(int); //指向函数的指针注意:其中的括号是必不可少的,它表明fp是一个指向返回值为void的函数的指针,而不是返回值为void*的函数。 指向函数的指针可为空,否则它应该指向一个具有适当类型的函数:extern int acef(int);extern void aceg(long);

ASCE1885 975

《认清C++语言》---引用和指针

1、  首先要认识到不存在类似于空引用(null reference)这样的东西。引用必须要指代某个对象,因此,如果有一个用于指代另外一个对象的变量,但是有可能没有这么一个可供指代的对象,这时就应该把这个变量声明成指针,因为这样就可以将它设为空值(null)了。反之,如果这个变量必须指代一个对象,即我们将这个变量设计成不允许为空值,那么这时基本上应该将这个变量声明成引用。2、 因为引用总是

ASCE1885 1870
上一篇: 认清C++语言之《制造抽象基类》
下一篇: 《认清C++语言》---new and delete
ACE1985
博客等级 码龄17年 1307粉丝 308原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值