effective C++ 笔记-02-内存管理02

来源:互联网 发布:武神赵子龙网络直播 编辑:程序博客网 时间:2024/06/08 00:57

条款8. 写operator new和operator delete时要遵循常规

实际做起来也就是:要有正确的返回值;可用内存不够时要调用出错处理函数(见条款7);处理好0字节内存请求的情况。此外,还要避免不小心隐藏了标准形式的new,不
过这是条款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分配的内存有多大。

Airplane *pa = new 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));







原创粉丝点击