effective C++ 笔记-02-内存管理02
来源:互联网 发布:武神赵子龙网络直播 编辑:程序博客网 时间:2024/06/08 00:57
条款8. 写operator new和operator delete时要遵循常规
实际做起来也就是:要有正确的返回值;可用内存不够时要调用出错处理函数(见条款7);处理好0字节内存请求的情况。此外,还要避免不小心隐藏了标准形式的new,不
过这是条款9的话题
过这是条款9的话题
这样,非类成员形式的operator new的伪代码看起来会象下面这样:
void * operator new(size_t size) // operator new还可能有其它参数{ if (size == 0) { // 处理0字节请求时, size = 1; // 把它当作1个字节请求来处理 } while (1) { 分配size字节内存; if (分配成功) return (指向内存的指针); // 分配不成功,找出当前出错处理函数 new_handler globalHandler = set_new_handler(0); set_new_handler(globalHandler); if (globalHandler) (*globalHandler)(); else throw std::bad_alloc(); }}
条款7提到operator new内部包含一个无限循环,上面的代码清楚地说明了这一点——while (1)将导致无限循环。跳出循环的唯一办法是内存分配成功或出错处理函数完成了
很多人没有认识到的一点是operator new经常会被子类继承。这会导致某些复杂性。基类中的operator new可能会被调用去为一个子类对象分配内存
C++标准很怪异,其中之一就是规定所以独立的(freestanding)类的大小都是非零值。
如果想控制基于类的数组的内存分配,必须实现operator new的数组形式——operator new[]
基类的operator new[]会通过继承的方式被用来为子类对象的数组分配内存,而子类对象往往比基类要大。所以,不能想当然认为Base::operator new[]里的每个对象的大小都是sizeof(Base),也就是说,数组里对象的数量不一定就是(请求字节数)/sizeof(Base)。
重写operator new(和operator new[])时所有要遵循的常规就这些。对于operator delete(以及它的伙伴operator delete[]),情况更简单。所要记住的只是,C++保证删除空指针永远是安全的,所以你要充分地应用这一保证。下面是非类成员形式的operator delete的伪代码:
void operator delete(void *rawMemory){ if (rawMemory == 0) return; file://如果指针为空,返回 // 释放rawMemory指向的内存; return;}
对于类的情况:
class Base { // 和前面一样,只是这里声明了public: // operator delete static void * operator new(size_t size); static void operator delete(void *rawMemory, size_t size); ...};void Base::operator delete(void *rawMemory, size_t size){ if (rawMemory == 0) return; // 检查空指针 if (size != sizeof(Base)) { // 如果size"错误", ::operator delete(rawMemory); // 让标准operator来处理请求 return; } 释放指向rawMemory的内存; return;}
条款9. 避免隐藏标准形式的new
内部范围声明的名称会隐藏掉外部范围的相同的名称
class X {public: // operator new的参数指定一个 // new-hander(new的出错处理)函数 static void * operator new(size_t size, new_handler p);};void specialErrorHandler(); // 定义在别的地方X *px1 = new (specialErrorHandler) X; // 调用X::operator newX *px2 = new X; // 错误!在类里定义了一个称为“operator new”的函数后,会不经意地阻止了对标准new的访问
解决办法:
一个办法是:
class X {public: static void * operator new(size_t size, new_handler p); static void * operator new(size_t size) { return ::operator new(size); }};X *px1 = new (specialErrorHandler) X; // 调用 X::operator // new(size_t, new_handler)X* px2 = new X; // 调用 X::operator // new(size_t)
另一种方法:
class X {public: static void * operator new(size_t size, // p缺省值为0 new_handler p = 0); //};X *px1 = new (specialErrorHandler) X; // 正确X* px2 = new X; // 也正确
条款10. 如果写了operator new就要同时写operator delete
让我们回过头去看看这样一个基本问题:为什么有必要写自己的operator new和operator delete?
答案通常是:为了效率
因为缺省版本的operator new是一种通用型的内存分配器,它必须可以分配任意大小的内存块。同样,operator delete也要可以释放任意大小的内存块。
operator delete想弄清它要释放的内存有多大,就必须知道当初operator new分配的内存有多大。
operator delete想弄清它要释放的内存有多大,就必须知道当初operator new分配的内存有多大。
Airplane *pa = new Airplane;
你不会得到一块看起来象这样的内存块:
pa——> Airplane 对象的内存
而是得到象这样的内存块:
pa——> 内存块大小数据 + Airplane 对象的内存
你不会得到一块看起来象这样的内存块:
pa——> Airplane 对象的内存
而是得到象这样的内存块:
pa——> 内存块大小数据 + Airplane 对象的内存
解决方案:自定义operator new + 内存池
class Airplane { // 修改后的类 — 支持自定义的内存管理public: // static void * operator new(size_t size); ...private: union { AirplaneRep *rep; // 用于被使用的对象 Airplane *next; // 用于没被使用的(在自由链表中)对象 }; // 类的常量,指定一个大的内存块中放多少个 // Airplane 对象,在后面初始化 static const int BLOCK_SIZE; static Airplane *headOfFreeList;};void * Airplane::operator new(size_t size){ // 把“错误”大小的请求转给::operator new()处理; // 详见条款8 if (size != sizeof(Airplane)) return ::operator new(size); Airplane *p = // p指向自由链表的表头 headOfFreeList; // // p 若合法,则将表头移动到它的下一个元素 // if (p) headOfFreeList = p->next; else { // 自由链表为空,则分配一个大的内存块, // 可以容纳BLOCK_SIZE个Airplane对象 Airplane *newBlock = static_cast<Airplane*>(::operator new(BLOCK_SIZE * sizeof(Airplane))); // 将每个小内存块链接起来形成一个新的自由链表 // 跳过第0个元素,因为它要被返回给operator new的调用者 for (int i = 1; i < BLOCK_SIZE-1; ++i) newBlock[i].next = &newBlock[i+1]; // 用空指针结束链表 newBlock[BLOCK_SIZE-1].next = 0; // p 设为表的头部,headOfFreeList指向的 // 内存块紧跟其后 p = newBlock; headOfFreeList = &newBlock[1]; } return p;}这个版本的operator new将会工作得非常好。因为通用型的缺省operator new必须应付各种大小的内存请求,还要处理内部外部的碎片
operator new和operator delete必须同时写,这样才不会出现不同的假设
void Airplane::operator delete(void *deadObject, size_t size){ if (deadObject == 0) return; // 见条款 8 if (size != sizeof(Airplane)) { // 见条款 8 ::operator delete(deadObject); return; } Airplane *carcass = static_cast<Airplane*>(deadObject); carcass->next = headOfFreeList; headOfFreeList = carcass;}
这里没有内存泄露!
引起内存泄露的原因在于内存分配后指向内存的指针丢失了。
每个大内存块首先被分成Airplane 大小的小块,然后这些小块被放在自由链表上。当客户调用Airplane::operator new时,小块被自由链表移除,客户得到指向小块的指针。当客户调用operator delete时,小块被放回到自由链表上。
然而确实,::operator new 返回的内存块是从来没有被Airplane::operatordelete 释放,这个内存块有个名字,叫内存池。
修改Airplane的内存管理程序使得::operator new 返回的内存块在不被使用时自动释放并不难,但这里不会这么做
内存池不能解决所有的内存管理问题,在很多情况下是很适合的
一定有什么办法把这种固定大小内存的分配器封装起来,从而可以方便地使用
一个可能的实现:
class Pool {public: Pool(size_t n); // 为大小为n的对象创建 // 一个分配器 void * alloc(size_t n) ; // 为一个对象分配足够内存 // 遵循条款8的operator new常规 void free( void *p, size_t n); // 将p所指的内存返回到内存池; // 遵循条款8 的operator delete常规 ~Pool(); // 释放内存池中全部内存};class Airplane {public: ... // 普通Airplane功能 static void * operator new(size_t size); static void operator delete(void *p, size_t size);private: AirplaneRep *rep; // 指向实际描述的指针 static Pool memPool; // Airplanes 的内存池};inline void * Airplane::operator new(size_t size){ return memPool.alloc(size); }inline void Airplane::operator delete(void *p,size_t size){ memPool.free(p, size); }// 为Airplane对象创建一个内存池,// 在类的实现文件里实现Pool Airplane::memPool(sizeof(Airplane));
- effective C++ 笔记-02-内存管理02
- effective C++ 笔记-02-内存管理01
- effective C++ 笔记-02-内存管理03
- effective objective-c 2.0 笔记 第五章 :内存管理
- 《Effective C++》阅读笔记02
- effective C++(第二章 内存管理)
- Effective C++学习笔记-内存管理
- C++内存管理------>以对象管理资源(Effective C++)
- 【Effective Objective-C 2.0读书笔记】第五章:内存管理
- Effective C++阅读笔记(一):内存管理
- C++-内存管理(整理笔记)
- C/C++内存管理 笔记
- C 内存管理学习笔记
- C++-内存管理(整理笔记)
- Effective C++:条款02
- Effective.C 读书笔记02
- 《Effective C++》02总结
- 《Effective C++》学习笔记条款13 以对象管理资源
- hdu_2039_三角形_解题报告
- extjs -- 编辑表格EditorGrid
- showModelDialog的使用(一)
- (七)Hibernate之JPA
- 如何在C++程序中设置XP样式?
- effective C++ 笔记-02-内存管理02
- VC6字体设置
- 一种数据展示方式,UI设计新颖,供大家参考(源码部分) (demo已经上传)
- css外边距合并
- HDU 1824 Let's go home (2-SAT)
- 读《Boost程序库完全开发指南》
- (八)Hibernate之JAP使用
- ArcGis开发(二)—基于FlexAPI-Tile数据
- 直方图均衡化与规定化