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

条款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 new
X *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));







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值