第8章 定制 new 和 delete
Customizing new and delete当计算机环境(例如Java和.NET)夸耀自己内置"垃圾回收能力"的当今,C++对内存管理的纯手工法也许看起来有点老气.但是许多苛刻的系统程序开发人员之所以选择C++,就是因为它允许他们手工管理内存.这样的开发人员研究并学习他们的软件使用内存的行为特征,然后修改分配和归还工作,以求获得其所建置的系统的最佳效率.
这样做的前提是,了解C++内存管理例程的行为.这正是本章焦点.两个主角是分配例程和归还例程(allocation and deallocation routines,也就是 operator new 和 operator delete),配角是 new-handler,这是当 operator new 无法满足客户的内存需求时所调用的函数.
多线程环境下的内存管理,遭受单线程系统不曾有过的挑战. 由于heap是一个可被改动的全局性资源,因此多线程系统充斥着访问这一类资源的race conditions(竞速状态)出现机会.
另外要记住的是, operator new 和 operator delete 只适合用来分配单一对象.Arrays所要的内存由 operator new[]分配出来,并由 operator delete[]归还(注意这两个函数名称中的[]).
最后请注意, STL容器所使用的heap内存是由容器所拥有的分配器对象(allocator object)管理,不是被 new 和 delete 直接管理.
条款49: 了解 new-handler的行为
Understand the behavior of the new-handler当 operator new 无法满足某一内存分配需求时,它会抛出异常. 当 operator new 抛出异常以反映一个未或满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的 new-handler.为了指定这个"用以处理内存不足"的函数,客户必须调用set_new_handler,那是声明于<new>的标准程序库函数:
namespace std {
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
}
如上所示,new_handler是个 typedef,定义出一个指针,该指针指向一个函数,这个函数参数为无,返回值类型为 void .set_new_handler则是"获得一个new_handler并返回一个new_handler"的函数.set_new_handler声明式尾端的一个"throw()"是一份异常明细,表示该函数不抛出任何异常.
set_new_handler的参数是个指针,指向 operator new 无法分配足够内存时才被调用的函数.其返回值也是个指针,指向set_new_handler被调用前正在执行(但马上就要被替换)的那个 new-handler函数.
可以这样使用set_new_handler:
// 以下是当operator new无法分配足够内存时,该被调用的函数
void outOfMem() {
std::cerr << "Unable to satify request for memory\n";
std::abort();
}
int main() {
std::set_new_handler(outOfMem);
int* pBigDataArray = new int[100000000L];
...
}
如果 operator new 无法为100000000个整数分配足够空间,outOfMem就会被调用,于是程序在发出一个信息后夭折(abort).
当 operator new 无法满足内存申请时,它会不断调用 new-handler函数,直到找到足够内存.引起反复调用的代码显示于条款51.这里的高级描述已经足够获得一个结论,那就是一个 设计良好的 new-handler函数必须做以下事情:
1.让更多内存可被使用. 这便造成 operator new 内的下一次内存分配动作可能成功.实现此策略的一个做法是,程序一开始执行就分配一大块内存,而后当 new-handler第一次被调用,将它们释放给程序使用.
2.安装另一个 new-handler. 如果目前这个 new-handler无法取得更多可用内存,或许它知道另外哪个 new-handler有此能力.
3.卸除 new-handler. 也就是将null指针传给set_new_handler .一旦没有安装任何 new-handler,operator new 会在内存分配不成功时抛出异常.
4.抛出 bad_alloc(或派生自bad_alloc)的异常. 这样的异常不会被 operator new 捕捉,因此会被传播到内存索求处.
5.不返回. 通常调用abort或exit.
假设打算处理Widget class 的内存分配失败情况.首先必须登录"当operator new无法为一个Widget对象分配足够内存时"调用的函数,所以需要声明一个类型为new_handler的 static 成员函数,用以指向 class Widget的 new-hanler.看起来像这样:
class Widget {
public:
static std::new_handler set_new_handler(std::new_handler p) throw();
static void* operator new(std::size_t size) throw(std::bad_alloc);
private:
static std::new_handler currentHandler;
};
static 成员必须在 class 定义式之外被定义(除非它们是 const 而且是整数型,详见
条款2),所以需要这样写:
std::new_handler Widget::currentHandler = 0;
Widget内的set_new_handler 函数会将它获得的指针存储起来,然后返回先前存储的指针,这也正是标准版set_new_handler的行为:
std::new_handler Widget::set_new_handler(std::new_handler p) throw() {
std::new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
最后Widget的 operator new 做以下事情:
1.调用标准set_new_handler,告知Widget的错误处理函数.
2.调用global operator new,执行实际的内存分配.
3.如果global operator new,能够分配足够一个Widget对象所用的内存,Widget的 operator new 会返回一个指针,指向分配所得.
注意:
set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用.