《认清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*);

};

 

 

 

 

 

VS code 调试显示preLaunchTask“g++“已终止,退出代码为-1。 生成‘... .exe‘文件错误 总结: 以后每个工程都需要按照这样的流程:创建一个.c/.cpp文件 -> F5运行 -> 配置tasks.json文件-> launch.json文件 -> 运行 阅读详情

相关推荐

ANSYS Fluent 2023R2实战:用欧拉多相流模型搞定流化床模拟(附详细参数避坑)

本文详细解析了ANSYS Fluent 2023R2中欧拉多相流模型在流化床模拟中的应用,包括模型选择、材料属性设置、相间作用力配置及初始化技巧。特别针对高颗粒浓度工况提供参数优化方案,帮助工程师提升模拟精度与效率,适用于化工、能源等领域的流化床反应器设计。

weixin_30938149的博客 268

【C++】堆对象

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

皓皓松的博客 1830

Python3 ACM模式输入输出全攻略:从牛客网到华为机试的实战技巧

本文详细解析了Python3在ACM模式下的输入输出处理技巧,针对牛客网、华为机试等在线评测系统。文章从input()函数本质出发,总结了六种高频输入模式的处理模板,并提供了链表、二叉树等数据结构的I/O实战方案,最后给出性能优化与常见避坑指南,帮助开发者高效应对算法笔试。

zero1的博客 998

6.4 子对象和堆对象

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

tang7mj的博客 649

点击率预测的贝叶斯平滑

概述 电商领域中经常需要计算或预测一些转化率指标,如最典型的CTR(点击率,Click-Through Rate)。这些转化率可以是模型的预测值,也可以作为模型的特征(feature)使用。以商品点击率预测为例,CTR的值等于点击量(Click)除以曝光量(Impression或Exposure)。以rr表示点击率, r=CI(1)r=\frac{C}{I} \tag{1}. 但在实际应

小平子的专栏 3万+

S32K3双核实战:手把手教你用EB配置CAN与CANFD(附中断/轮询代码对比)

本文详细解析了NXP S32K3双核处理器在CAN/CANFD通信开发中的实战应用。通过Elektrobit配置工具链,从双核通信架构设计到EB tresos Studio配置全流程,再到中断与轮询模式的代码实现对比,全面指导开发者优化S32K3 FlexCAN模块性能,提升汽车电子系统的通信效率与可靠性。

weixin_30955617的博客 440

堆对象

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

867

一文读懂 MAVLink、PPM、SBUS:无人机通信协议的工作原理与适用场景

本文系统对比无人机领域三大核心通信协议:MAVLink作为全链路数据协议,支持飞控与地面站的双向通信,适合航拍测绘等专业场景;SBUS是数字化的高效控制协议,具备16通道、抗干扰特性,成为中高端设备标配;PPM作为传统模拟协议,成本低廉但性能有限,适合入门级应用。文章深入解析各协议的工作原理、帧结构、适用场景及调试技巧,指出MAVLink侧重状态监控、SBUS/PPM专注控制指令的互补关系,并给出新手选型建议:入门选PPM,进阶用SBUS,开发必备M

bother3000的博客 7124

C++复杂对象之堆对象

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

life is wonderful 1719

Virtual Hard Disk version 2 (VHDX) 文件格式详解

VHDX 镜像格式详解 0. References windows github 1. Overview Microsoft 虚拟化产品中常用的映像格式是虚拟硬盘版本 2 (VHDX) 映像格式。该规范基于可用文档,并通过分析测试数据进行了增强。 VHDX 镜像格式种类 固定硬盘镜像 动态硬盘镜像 差异硬盘镜像 VHDX 镜像文件由以下元素构成: file (type) identifier 2x image headers 2x region tables log or metadata journ

圣三一 2696

SAP成本核算的核心逻辑

SAP成本核算采用"计划-归集-分析"闭环管理,以CO模块为核心,集成PP、MM等模块。核算前需配置标准成本控制范围、成本中心等主数据。标准成本通过BOM和工艺路线计算(CK11N),采购和生产过程记录实际成本差异。制造费用通过成本中心归集,作业成本按实际耗用量分配。完工产品按标准成本入库,月末通过KO88结算工单差异,物料分类账(CKMLCP)还原实际成本。整个流程实现FI与CO数据同步,确保账务一致。关键事务码包括CK11N(成本估算)、MIGO(物料移动)、CO11N(报工)等。

金牌架构师 537

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

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

编程猿来如此 1029

GTK4 入门到实战

本文介绍GTK4作为主流Linux GUI工具包的发展历程与核心特性,涵盖其从GIMP项目诞生的自由软件理念、版本演进及跨平台支持。重点解析GTK4的架构(GDK/GSK/GLib等)、GObject对象系统、信号-回调机制与事件驱动模型,并以C语言为例,结合开发环境搭建与官方示例(gtk4-demo)指导实践,最终通过图片查看器与多标签编辑器项目实现完整应用开发。

OneSea 1396

《认清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

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

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

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

ASCE1885 1623

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

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

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

ASCE1885 1777

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

认清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、付费专栏及课程。

余额充值