1. 当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型参数用于指定当无法分配足够内存时调用的函数,set_new_handler返回的函数指针指向在此之前用处处理内存不足当马上就要被替换的函数,set_new_handler的用法如下:
//outOfMem是无法分配足够内存时,应该被调用的函数 void outOfMem(){ std::cerr<<"Unable to satisfy request for mempry/n"<<endl; std::abort(); } int main(){ std::set_new_handler(outOfMem); int* pBigDataArray=new int[1000000000]; ... }
如果operator new无法分配1000000000个int变量所需空间,它不会立即抛出异常,而是调用outOfMem,因为outOfMem已经被设置为默认的new-handler.
一个设计良好的new-handler应该做以下事情:
1). 让更多内存可被使用.实现这个目的的策略之一是,在程序一开始就分配一大块内存,当new-handler第一次被调用(说明内存不足)时,就把它们归还给程序使用.
2). 安装另一个new-handler,如果当前new-handler无法获取更多内存但它知道另一个new-handler有此能力,它可以调用set_new_handler将那个new-handler设为默认new-handler使得下一次operator new调用的new-handler是最新的那个(令一种策略是令当前new-handler修改自己的行为,方法是让它修改会影响当前new-handler行为的static数据,namespace数据,global数据等)
3). 卸除new-handler.将NULL指针传给set_new_handler,当operator new分配内存不成功时抛出异常.
4). 抛出bad_alloc(或派生自bad_alloc的)异常.这样的异常不会被operator new捕捉,因而会被传递到内存索求处.
5). 不返回.调用abort或exit.(正如outOfMem所做的那样)
2. 有时对于不同类,希望以不同new-handler处理operator new内存分配失败的情况.虽然C++不支持class专属之new-handler,但可以自己实现这种行为,只需令每一个class提供自己的set_new_handler和operator new即可.其中set_new_handler用于设定class专属之new-handler,operator new确保在分配class内存时以class专属之new-handler替换global new-handler,并在class专属之new-handler完成职责后将默认new-handler替换为global new-handler.
假设要处理Widget class的内存分配失败情况,那么以上策略实现如下:
class Widget{ public: static std::new_handler set_new_handler(std::new_handler p) throw(); static std::operator new(std::size_t size) throw(std::bad_alloc); private: static std::new_handler currentHandler; } std::new_handler Widget::currentHandler=0; 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的错误处理函数,即将Widget的new-handler安装位global new-handler.
2). 调用global operator new用来实际分配内存,如果分配失败,global operator new会调用Widget的new-handler,因为那个函数才刚被安装为new-handler.如果global operator new最终无法分配足够内存,会抛出一个bad_alloc异常,在此情况下Widget的operator new必须恢复原本的global new-handler,然后再传播该异常,这就需要将采用条款13的思想——以资源管理对象.
3). 最后,如果global operator new成功分配一个Widget对象所用内存,Widget的operator new应该返回一个指针,指向分配所得.
结合资源管理类实现Widget::operator new如下:
class NewHandlerHolder{ public: explict NewHandlerHolder(std::new_handler nh):handler(nh){} ~NewHandler(){std::set_new_handler(handler);} private: std::new_handler handler; //用于保存global new-handler NewHandlerHolder(const NewHandlerHolder&); NewHandlerHolder& operator=(const NewHandlerHolder&); } void* Widget::operator new(std::size_t size) throw(std::bad_alloc){ NewHandlerHolder h(std::set_new_handler(currentHandler));//安装new-handler并使h保存global new-handler return ::operator new(size); //h析构时恢复global new-handler }
Widget的客户类似这样使用其new-handler:
void ourOfMem(); Widget::set_new_handler(outOfMem); Widget* pw1=new Widget; //如果内存分配失败,调用的是outOfMem std::string *ps=new string; //如果内存分配失败,调用的是global new-handler Widget::set_new_handler(0); Widget* pw2=new Widget; //如果内存分配失败,直接抛出异常
以上代码具有一般化,因此可以考虑将其设为模板,由其他类继承,从而继承这种"可以设定类之专属new-handler"的能力:
template<typename T> class NewHandlerSupport{ public: static std::new_handler set_new_handler(std::new_handler p) throw(); static std::operator new(std::size_t size) throw(std::bad_alloc); ... private: static std::new_handler currentHandler; } template<typename T> std::new_handler NewHandlerSupport<T>::currentHandler=0; template<typename T> std::new_handler NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw(){ std::new_handler oldHandler=currentHandler; currentHandler=p; return oldHandler; } template<typename T> void* NewHandlerSupport<T>::::operator new(std::size_t size) throw(std::bad_alloc){ NewHandlerHolder h(std::set_new_handler(currentHandler));//安装new-handler并使h保存global new-handler return ::operator new(size); //h析构时恢复global new-handler }
有了这个class template,就可以为Widget和其他类添加set_new_handler支持能力了——只要令Widget继承自NewHandlerSupport<Widget>就好:
class Widget:public NewHandlerSupport<Widget>{ ... }
类模板NewHandlerSupport看起来相当奇怪,因为参数T从未被使用,实际上参数T只是用来区分不同的derived class,使继承自NewHandlerSupport的每一个class都有自己独立的static成员变量currentHandler.至于Widget继承自一个以Widget为参数的类模板具现化的类,这是被C++允许的,而且拥有自己的名称——"怪异的循环模板模式"(curiously recurring template pattern;CRTP).
NewHandlerSupport类模板的构建使得"为任何class添加一个它们专属的new-handler"成为易事,然而"mixin"风格的继承亦会导致多重继承的争议.
3. 直至1993年之前,C++还要求operator new必须在无法分配足够内存时返回NULL,虽然新一代的operator应该抛出bad_alloc异常,但原本的"nothrow"形式的operator new仍然保存下来,定义于头文件<new>,调用方法如下:
class Widget{...} Widget* pw1=new Widget;
由于以上new Widget表达式发生两件事:调用nothrow版的operator new,调用Widget的默认构造函数,因而尽管nothrow版的operator new保证不抛出异常,但这并不能阻止Widget的默认构造函数抛出异常,因而nothrow new不能阻止new表达式抛出异常.