




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、深入c+的newnew是c+的一个关键字,同时也是操作符关于new的话题非常多,因为它确实比较复 杂,也非常神秘,下面我将把我了解到的与new有关的内容做一个总结new的过程当我们使用关键字new在堆上动态创婕一个对象时,它实际上做了三件事:获得一块 内存空间调用构造函数返回止确的指针当然,如果我们创建的是简单类型的变量,那么第 二步会被省略假如我们定义了如下一个类a:class aint i;public:a(intj) :i(_i*_i) void say() printf(,i=%dnn, i);;调用new:a* pa = new a(3);那么上述动态创建一个对象的过程大致相当于以卜
2、-三句话(只是大致上):虽然从效果上看,这三句话也得到了一个有效的指向堆上的a对彖的指针pa,但区别 在于,当malloc火败时,它不会调用分配内存火败处理程序new_handler,而使用new的 话会的因此我们还是要尽可能的使用new,除非有一些特姝的需求 new的三种形态到h前为止,本文所捉到的new都是指的new operator或称为new expression,但事 实上在c+中一提到new,至少可能代表以下三种含义:new operatoroperator newplacement newnew operator就是我们平吋所使用的new,其行为就是前而所说的三个步骤,我们不 能
3、更改它但具体到某一步骤屮的行为,如果它不满足我们的具体要求时,我们是有可能更 改它的三个步骤屮最后一步只是简单的做一个指针的类型转换,没什么可说的,并且在编 译出的代码中也并不需要这种转换,只是人为的认识罢了但前两步就有些内容了new operator的第一步分配内存实际上是通过调川operator new來完成的,这里的 new实际上是像加减乘除一样的操作符,因此也是川以重载的operator new默认情况卜'首 先调用分配内存的代码,尝试得到一段堆上的空间,如果成功就返冋,如杲失败,则转而 去调川一个new_hander,然后继续重复前面过程如果我们对这个过程不满意,就可以重 载
4、operator new,来设置我们希瑕的行为例如:class apublic:void* operator new(size_t size)printf("operator new calledn");return operator new(size);;a* a = new a();这里通过:operator new调川了原有的全局的new,实现了在分配内存之前输出一句话 全局的operator new也是可以重载的,但这样一來就不能再递归的使用new來分配内存, 而只能使用malloc t:void* operator new(size_t size)printf(”
5、global newn”);return malloc(size);相应的,delete也有delete operator和operator delete之分,后者也是可以重载的并 且,如果重载了 operator new,就应该也相应的重载operator delete,这是良好的编程习 惯new的第三种形态placement new是用来实现定位构造的,因此可以实现new operator三步操作中的第二步,也就是在取得了一块可以容纳指定类型对象的内存后,在 这块内存上构造一个对象,这有点类似于前面代码中的p->a:a;这句话,但这并不是一 个标准的写法,正确的写法是使用placem
6、ent new:#inelude <new.h>void main()char ssizeof(a);a*p = (a*)s;new(p) a(3); /p->a:a(3);p->say();对头文件vnew>或vnew.h>的引丿u是必须的,这样才可以使川placement new这里 new(p) a这种奇怪的写法便是placement new 了,它实现了在指定内存地址上用指定类 型的构造函数來构造一个对象的功能,后面a(3)就是对构造函数的显式调用这里不难发 现,这块指定的地址既可以是栈,又可以是堆,placement对此不加区分但是,除非特别 必要
7、,不要直接使川placement new ,这毕竟不是用来构造对象的正式写法,只不过是 new operator的一个步骤而己使用new operator地编译器会动生成对placement new的 调用的代码,因此也会相应的生成使用delete时调用析构函数的代码如果是像上面那样在 栈上使用了 placement new,则必须手工调用析构函数,这也是显式调用析构函数的唯一 情况:p->a();当我们觉得默认的new operator对内存的管理不能满足我们的需要,而希望口己手工 的管理内存时,placement new就有用了 stl中的allocator就使用了这种方式,借助 p
8、lacement new来实现更灵活有效的内存管理处理内存分配异常正如前而所说,operator new的默认行为是请求分配内存,如果成功则返回此内存地 址,如果失败则调用一个new_handler,然后再重复此过程丁是,想要从operator new的 执行过程中返冋,则必然需要满足下列条件之一:于是,我们町以假设默认情况下operator new的行为是这样的:void* operator new(size_t size)void* p = nullwhile(!(p = malloc(size)if(null = new_handler)throw bad_alloc();tryn ew
9、_ha ndler();catch(bad_alloc e)throw e;catch()return p;在默认情况下,new_handler的行为是抛出一个bad_alloc异常,因此上述循环只会执 行一次但如杲我们不希望使用默认行为,可以自定义一个new_handler,并使用 std:set_new_handler函数使其生效在自定义的new_handler中,我们可以抛出异常,可 以结束程序,也可以运行一些代码使得有可能有内存被空闲出来,从而下一次分配时也许 会成功,也可以通过set_new_handler来安装另一个可能更有效的new_handler例如: void mynewha
10、ndler()printf(new handler called!n);throw std:bad_alloc();std:set_ new_ha ndler(mynewha ndler);这里new_handler程序在抛出异常z前会输出一句话应该注意,在new_handler的代 码里应该注意避免再嵌套有对new的调川,因为如杲这甲调用new再失败的话,可能会再 导致对new handler的调用,从而导致无限递归调用这是我猜的,并没有尝试过在编程时我们应该注意到对new的调用是有可能有异常被抛出的,因此在new的代码 周围应该注意保持其事务性,即不能因为调川new失败抛出异常来导致不正确
11、的程序逻辑 或数据结构的iii现例如:class someclassstatic int count;someclass() public:static someclass* getnewlnstance()coun t+;return new someclass();;静态变量count用于记录此类型生成的实例的个数,在上述代码中,如果因new分配 内存失败而抛出异常,那么其实例个数并没有增加,但count变量的值却已经多了一个, 从而数据结构被破坏正确的写法是:static someclass* getnewlnstance()someclass* p = new someclass();
12、count+;return p;这样一来,如果new失败则直接抛出异常,count的值不会增加类似的,在处理线程 同步时,也要注意类似的问题:void somefunc()lock(somemutex); 加一个锁delete p;p = new someclass();unlock(somemutex);stl的内存分配ai traits技巧在stl原码剖析一书屮详细分析了 sgi stl的内存分配器的行为与直接使川new operator不同的是,sgistl并不依赖c+默认的内存分配方式,而是使用一-套自行实现 的方案首先sgi stl将可用内存整块的分配,使z成为当前进程可用的内存,当
13、程序中确 实需要分配内存时,先从这些匚请求好的大内存块中尝试取得内存,如果失败的话再尝试 整块的分配大内存这种做法有效的避免了大量内存碎片的出现,捉高了内存筲理效率为了实现这种方式,stl使mjt placement new,通过在自己管理的内存空间上使用 placement new来构造对彖,以达到原有new operator所具有的功能template <class t1, class t2>inline void construct(t 1 * p, const t2& value) 此函数接收一个已构造的对象,通过拷贝构造的方式在给定的内存地址p上构造一个 新对彖,
14、代码中后半截t1 (value)®是placement new语法中调用构造函数的写法,如果传 入的对象value te是所要求的类型t1,那么这里就相当于调川拷贝构造函数类似的,因使 用了 placement new,编译器不会口动产牛调用析构函数的代码,需要手工的实现: template <class t>inline void destory(t* pointer)与此同时,stl中还有一个接收两个迭代器的destory版本,可将某容器上指定范围内 的対象全部销毁典型的实现方式就是通过一个循环來对此范围内的対象逐一调用析构函数 如果所传入的对象是非简单类型,这样做是
15、必要的,但如果传入的是简单类型,或者根本 没有必要调用析构函数的自定义类型(例如只包含数个int成员的结构体),那么再逐一调 用析构函数是没有必要的,也浪费了时间为此,stl使用了-种称为type traits的技巧,在 编译器就判断出所传入的类型是否需要调川析构函数:template <class forwardlterator>inline void destory( forward iterator first, forward iterator last)_destory(first, last, value_type(first);其屮valuejypeojij于取出迭代
16、器所指向的对象的类型信息,于是:template<class forward iterator, class t>inline void _destory(forward iterator first, forwarditerator last, t*)typedef type name _type_traits<t>:has_trivial_destructor trivial_destructor;_destory_aux(first, last, trivial_destructor();如果霊要调用析构函数:template<class forwardlt
17、erator>inline void _destory_aux( forward iterator first, forward iterator last, _false_type)for(; first < last; +first)destory(&州rst);/因first是迭代器first取出其真正内容,然后再丿ij&取地址如果不需要,就什么也不做:tempalte<class forwardlterator>inline void _destory_aux(forward iterator first, forward iterator la
18、st, _true_type) 因上述苗数全都是inline的,所以多层的断数调用并不会对性能造成影响,最终编译 的结果根据具体的类型就只是一个for循坏或者什么都没有这里的关键在于type_traitsvt>这个模板类上,它根据不同的t类型定义出不同的has_trivial_destructor 的结果,如果t是简单类型,就定义为_true_type类型,否则就定义为_false_type类型 其中_truejype_falsejype只不过是两个没有任何内容的类,对程序的执行结果没有什 么意义,但在编译器看来它对模板如何特化就具有非常重要的指导意义了,正如上面代码 所示的那样_typ
19、e_traitsvt >也是特化了的一系列模板类:struct _truejype ;struct _falsejype ;template <class t>struct _type_traitspublic:typedef _false _type has_trivial_destructor;;templateo 模板特化struct _type_traits<int>/int 的特化版本public:typedef _truejype has_trivial_destructor;;其他简单类型的特化版本如果要把一个自定义的类型myclass也定义为不调川
20、析构函数,只需要相应的定义type_traitsvt>的一个特化版木即可:templateostruct _type_traits<myclass>public:typedef _truejype has_trivial_destructor;;模板是比较高级的c+编程技巧,模板特化模板偏特化就更是技巧性很强的东西,stl中的typejraits充分借助模板特化的功能,实现了在程序编译期通过编译器來决定为 每一处调用使用哪个特化版本,于是在不增加编程复杂性的前提下大大提高了程序的运行 效率更详细的内容可参考stl源码剖析第二三章屮的相关内容 帯有的new和delete我们经常
21、会通过new来动态创建一个数组,例如:严格的说,上述代码是不止确的,因为我们在分配内存时使用的是new,而并不是简 单的new,但释放内存时却用的是delete正确的写法是使用delete: delete s;但是,上述错误的代码似乎也能编译执行,并不会带来什么错误事实上,new与 newdelete为delete是有区别的,特别是当用来操作复杂类型时假如针对一个我们口定 义的类myclass使川new:myclass* p = new myclass10;上述代码的结果是在堆上分配了 10个连续的myclass实例,并且已经对它们依次调川 了构造函数,于是我们得到了 10个可用的对象,这一点
22、与javac#有区別的,javac#中这 样的结果只是得到了 10个null换句话说,使用这种写法时myclass必须拥有不带参数的构 造函数,否则会发现编译期错误,因为编译器无法调用有参数的构造函数当这样构造成功后,我们可以再将其释放,释放时使川delete:delete p;当我们对动态分配的数组调川deleted时,其行为根据所申请的变量类型会有所不同如 果p指向简单类型,如intchar等,其结果只不过是这块内存被回收,此时使用delete与 delete没有区别,但如果p指向的是复杂类型,delete会针对动态分配得到的每个对象调 川析构函数,然后再释放内亦因此,如果我们对上述分配得
23、到的p指针宜接使川delete来 冋收,虽然编译期不报什么错误(因为编译器根本看不出來这个指针p是如何分配的), 但在运行时(debug情况下)会给出一个debug assertion failed 4s示到这里,我们很容易提出一个问题delete如何知道要为多少个对象调用析构函数 的?要回答这个问题,我们可以首先看一看new的重载class myclassint a;public:myclass() printf("ctorn,');myclass() printf(”dtorrt);;void* operator new(size_t size)void* p = ope
24、rator new(size);printf(hcalling new with size=%d address=%prf, size, p);return p;/主函数myclass* me = new myclass3; printf("address of mc=%prt: me); delete me;运行此段代码,得到的结果为:(vc2005)ctorctorctordtordtordtor虽然对构造苗数和析构苗数的调用结果都在预料z中,但所中请的内存空间大小以及 地址的数值却出现了问题我们的类myclass的人小显然是4个字节,并请的数组屮有 3个元素,那么应该一共川请1
25、2个字节才对,但事实上系统却为我们申请了 16字节,并ii 在operator new返丿ff我们得到的内存地址是实际申请得到的內存地址值加4的结杲也就是 说,当为复朵类型动态分配数组时,系统自动在最终得到的内存地址前空出了 4个字节, 我们有理由相信这4个字节的内容与动态分配数组的长度有关通过单步跟踪,很容易发现 这4个字节对应的int值为0x00000003,也就是说记录的是我们分配的对彖的个数改变一 卜分配的个数然厉再次观察的结杲证实了我的想法于是,我们也有理由认为new operator 的行为相当于下血的伪代码:template <class t> t* new(int
26、count) int size = sizeof(t) * count + 4;void* p = t:operator new(size);*(int*)p = count;t* pt = (t*)(int)p + 4);for(int i = 0; i < count; i+)new(&pti)t();return pt;上述示意性的代码省略了异常处理的部分,只是展示当我们对一个复杂类型使用new 来动态分配数组时其真正的行为是什么,从中可以看到它分配了比预期多4个字节的内存 并用它来保存对象的个数,然后对于后而每一块空间使用placement new来调用无参构造 函数,这
27、也就解释了为什么这种情况下类必须有无参构造函数,最后再将首地址返冋类似 的,我们很容易写出相应的delete的实现代码:template <class t> void delete(t* pt) int count = (int*)pt)-1;for(int i = 0; i < count; i+)ptit();void* p = (void*)(int)pt 4);t:operator delete(p);由此可见,在默认情况下operator new与operator new的行为是相同的,operator delete叫 operator delete 也是,不同的是
28、 new operator 与 new operatordelete operator 与delete operator当然,我们口j以根据不同的需要來选择重载带有和不带有的operator new和delete,以满足不同的具体需求把前而类myclass的代码稍做修改注释掉析构函数,然厉再来看看程序的输出:calling new with size=12 address=003a5a58ctorctorctoraddress of mc=003a5a58这一次,newq老老实实的申请了 12个字节的内存,并且申请的结果与new operator 返冋的结果也是相同的,看来,是否在前而添加4个
29、字节,只取决于这个类有没有析构函 数,当然,这么说并不确切,正确的说法是这个类是否需要调用构造函数,因为如下两种 情况下虽然这个类没声明析构函数,但还是多申请了4个字节:一是这个类中拥有需要调 用析构函数的成员,二是这个类继承口需要调用析构函数的类丁是,我们可以递归的定义 需要调用析构函数的类为以卜三种情况之一:1显式的声明了析构函数的2拥有需要调川析构函数的类的成员的3继承自需要调用析构函数的类的类似的,动态申请简单类型的数组时,也不会多申请4个字节于是在这两种情况下, 释放内存时使用delete或deleted都可以,但为养成良好的习惯,我们还是应该注意只要是 动态分配的数组,释放时就使用
30、delete 释放内存时如何知道长度但这同时乂带来了新问题,既然申请无需调川析构函数的类或简单类型的数组时并没 有记录个数信息,那么operator delete,或更真接的说free()是如何来i口i收这块内存的呢? 这就要研究malloc()返回的内存的结构了与new类似的是,实际上在malloc()申请内存时 也多申请了数个字节的内容,只不过这与所申请的变量的类型没有任何关系,我们从调用 malloc时所传入的参数也可以理解这一点它只接收了要申请的内存的长度,并不关系这块 内存用来保存什么类型下血运行这样一段代码做个实验:char *p = 0;for(int i = 0; i < 40; i += 4)char* s = new chari;printf("alloc %2d bytes, address=%p distance=%dn”,i, s, s p);我们垃接來看v
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年石油化工安装工程内部承包合同示范文本
- 二零二五版智能家居装修设计与施工合同
- 二零二五年度车辆抵押担保汽车保险合同
- 二零二五版电视剧剧本创作合同终止协议
- 2025版高端酒店场地租赁合同模板
- 2025版商业地产场地租赁分成合同示范文本
- 2025版智能仓储厂房租赁安全协议范本
- 2025版早餐店连锁经营合作协议
- 2025版仓储物流园区建设承包管理合同样本
- 二零二五年度工业厂房消防喷淋系统升级改造合同
- 芜湖凤鸣控股集团(筹)2024年招聘工作人员笔试考点考试题库与答案
- 2025年云南省时事政治考试试卷带解析附完整答案(考点梳理)
- 2025春季学期国开电大本科《经济学(本)》一平台在线形考(形考任务1至6)试题及答案
- CJ/T 30-2013热电式燃具熄火保护装置
- 2025贵州省水利投资(集团)有限责任公司招聘84人笔试备考题库附答案详解(巩固)
- 调岗协议书合同补充
- 2025香河事业单位笔试真题
- 车子刮擦协议书
- 2025年山东省普通高校招生(春季高考)全省统一考试语文试题
- 诗歌鉴赏|苏轼《鹤叹》注释+赏析+试题
- 2025年护士考试理论知识整合试题及答案
评论
0/150
提交评论