Effective C++ 2e Item8

原创 2001年07月02日 21:01:00

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

自己重写operator new时(条款10解释了为什么有时要重写它),很重要的一点是函数提供的行为要和系统缺省的operator new一致。实际做起来也就是:要有正确的返回值;可用内存不够时要调用出错处理函数(见条款7);处理好0字节内存请求的情况。此外,还要避免不小心隐藏了标准形式的new,不过这是条款9的话题。

有关返回值的部分很简单。如果内存分配请求成功,就返回指向内存的指针;如果失败,则遵循条款7的规定抛出一个std::bad_alloc类型的异常。

但事情也不是那么简单。因为operator new实际上会不只一次地尝试着去分配内存,它要在每次失败后调用出错处理函数,还期望出错处理函数能想办法释放别处的内存。只有在指向出错处理函数的指针为空的情况下,operator new才抛出异常。

另外,C++标准要求,即使在请求分配0字节内存时,operator new也要返回一个合法指针。(实际上,这个听起来怪怪的要求确实给C++语言其它地方带来了简便)

这样,非类成员形式的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();
  }
}

处理零字节请求的技巧在于把它作为请求一个字节来处理。这看起来也很怪,但简单,合法,有效。而且,你又会多久遇到一次零字节请求的情况呢?

你又会奇怪上面的伪代码中为什么把出错处理函数置为0后又立即恢复。这是因为没有办法可以直接得到出错处理函数的指针,所以必须通过调用set_new_handler来找到。办法很笨但也有效。

条款7提到operator new内部包含一个无限循环,上面的代码清楚地说明了这一点——while (1)将导致无限循环。跳出循环的唯一办法是内存分配成功或出错处理函数完成了条款7所描述的事件中的一种:得到了更多的可用内存;安装了一个新的new-handler(出错处理函数);卸除了new-handler;抛出了一个std::bad_alloc或其派生类型的异常;或者返回失败。现在明白了为什么new-handler必须做这些工作中的一件。如果不做,operator new里面的循环就不会结束。

很多人没有认识到的一点是operator new经常会被子类继承。这会导致某些复杂性。上面的伪代码中,函数会去分配size字节的内存(除非size为0)。size很重要,因为它是传递给函数的参数。但是大多数针对类所写的operator new(包括条款10中的那种)都是只为特定的类设计的,不是为所有的类,也不是为它所有的子类设计的。这意味着,对于一个类X的operator new来说,函数内部的行为在涉及到对象的大小时,都是精确的sizeof(X):不会大也不会小。但由于存在继承,基类中的operator new可能会被调用去为一个子类对象分配内存:
class Base {
public:
  static void * operator new(size_t size);
  ...
};

class Derived: public Base       // Derived类没有声明operator new
{ ... };                         //

Derived *p = new Derived;        // 调用Base::operator new

如果Base类的operator new不想费功夫专门去处理这种情况——这种情况出现的可能性不大——那最简单的办法是把这个“错误”数量的内存分配请求转给标准operator new来处理,象下面这样:
void * Base::operator new(size_t size)
{
  if (size != sizeof(Base))             // 如果数量“错误”,让标准operator new
    return ::operator new(size);        // 去处理这个请求
                                        //

  ...                                   // 否则处理这个请求
}

“停!”我听见你在叫,“你忘了检查一种虽然不合理但是有可能出现的一种情况——size有可能为零!”是的,我没检查,但拜托下次再叫出声的时候不要这么文绉绉的。:)但实际上检查还是做了,只不过融合到size != sizeof(Base)语句中了。C++标准很怪异,其中之一就是规定所以独立的(freestanding)类的大小都是非零值。所以sizeof(Base)永远不可能是零(即使base类没有成员),如果size为零,请求会转到::operator new,由它来以一种合理的方式对请求进行处理。(有趣的是,如果Base不是独立的类,sizeof(Base)有可能是零,详细说明参见"my article on counting objects")。

如果想控制基于类的数组的内存分配,必须实现operator new的数组形式——operator new[](这个函数常被称为“数组new”,因为想不出"operator new[]")该怎么发音)。写operator new[]时,要记住你面对的是“原始”内存,不能对数组里还不存在的对象进行任何操作。实际上,你甚至还不知道数组里有多少个对象,因为不知道每个对象有多大。基类的operator new[]会通过继承的方式被用来为子类对象的数组分配内存,而子类对象往往比基类要大。所以,不能想当然认为Base::operator new[]里的每个对象的大小都是sizeof(Base),也就是说,数组里对象的数量不一定就是(请求字节数)/sizeof(Base)。关于operator new[]的详细介绍参见条款M8。

重写operator new(和operator new[])时所有要遵循的常规就这些。对于operator delete(以及它的伙伴operator delete[]),情况更简单。所要记住的只是,C++保证删除空指针永远是安全的,所以你要充分地应用这一保证。下面是非类成员形式的operator delete的伪代码:
void operator delete(void *rawMemory)
{
  if (rawMemory == 0) return;    file://如果指针为空,返回
                                 //

  释放rawMemory指向的内存;

  return;
}

这个函数的类成员版本也简单,只是还必须检查被删除的对象的大小。假设类的operator new将“错误”大小的分配请求转给::operator new,那么也必须将“错误”大小的删除请求转给::operator delete:

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;
}

可见,有关operator new和operator delete(以及他们的数组形式)的规定不是那么麻烦,重要的是必须遵守它。只要内存分配程序支持new-handler函数并正确地处理了零内存请求,就差不多了;如果内存释放程序又处理了空指针,那就没其他什么要做的了。至于在类成员版本的函数里增加继承支持,那将很快就可以完成。

编写高质量的iOS代码--Effective Objective-C 2.0 读书笔记

编写高质量的iOS代码--Effective Objective-C 2.0 读书笔记 这本书年初刷完,感觉不错,介绍了很多小点,都是平日不怎么关注的. 第1章 熟悉Objective-C...
  • uxyheaven
  • uxyheaven
  • 2014年12月26日 23:56
  • 5027

读书笔记_Effective C++_习惯C++

这是一本非常经典C++书籍,也是我在工作中发现自己C++上还有很多薄弱点的时候经常拿来充电的。这本书内容很多,讲了很多如何高效地使用C++的方法,有些地方自己也没能啃透,读过一遍后很多知识点容易忘记,...
  • John_cdy
  • John_cdy
  • 2015年05月04日 09:51
  • 2155

《Effective C++》:条款44-条款45

条款44将与参数无关的代码抽离templates 条款45运用成员函数模板接受所有兼容类型...
  • KangRoger
  • KangRoger
  • 2015年03月12日 22:01
  • 1478

Item 8:析构函数不要抛出异常 Effective C++笔记

Item 8: Prevent exceptions from leaving destructors. 析构函数不要抛出异常 由于析构函数常常被自动调用,在析构函数中抛出的异常往往会难以...
  • yangjvn
  • yangjvn
  • 2015年08月24日 13:20
  • 699

Effective C++ Item 8 别让异常逃离析构函数

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验1: 析构函数绝对不要吐出异常。如果一个被析构函数调用的...
  • CatEatApple
  • CatEatApple
  • 2014年10月28日 16:15
  • 424

Effective Modern C++ : Item 8 -> 优先选择nullptr而不是0和NULL

优先选择nullptr而不是0和NULL 我们知道:0是一个int,而不是一个指针。如果C++在一个只有指针才能够使用的上下文中发现它只有一个0,那么它会勉强将0解释成空指针,但那时一种倒退行为。C...
  • victorydh123
  • victorydh123
  • 2017年06月20日 14:04
  • 132

Effective Modern C++ Item 2

如果你已经读过条款1关于模板类型的推导,你几乎已经知道所有关于auto类型推导的知识了,因为除了一种特殊情况,auto类型推导就是模板类型推导。但怎么会这样?模板类型推导涉及模板和函数还有参数,但au...
  • Love_3_9
  • Love_3_9
  • 2017年08月03日 19:55
  • 183

Effective C++----3rd Edition, Item 2:用consts,enums和inlines取代#define

Item 2: 用 consts, enums 和 inlines 取代 #defines 作者:Scott Meyers 译者:fatalerror99 (iTePub's Nirvana) ...
  • qianqin_2014
  • qianqin_2014
  • 2016年05月10日 21:25
  • 214

effective C++ (Item2) Prefer <iostream> to <stdio.h>

type safety and extensibility are cornerstones of the C++ way of life.  int i; Rational r; cin >>...
  • yqdm_zju
  • yqdm_zju
  • 2011年11月03日 23:52
  • 630

Effective C++ (3rd Ed) 读书笔记(一)Item 2: Prefer constS, enumS, and inlineS to #defineS

#includecxvxv
  • persimmon2010
  • persimmon2010
  • 2013年11月27日 11:59
  • 304
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Effective C++ 2e Item8
举报原因:
原因补充:

(最多只允许输入30个字)