条款49:了解new-handler的行为
1、当operator new无法满足某一内存分配需求时,它会抛出异常,以前会返回null指针。当operator new抛出异常以反映一个未获满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的new-handler。为了指定这个“用以处理内存不足”的函数,客户必须调用set_new_handler,它是声明于<new>的一个标准程序库函数:
namespace std { typedef void(*new_handler)(); new_handler set_new_handler(new_handler p) throw(); //异常声明,表示该函数不抛出任何异常 }
set_new_handler的参数是个指针,指向operator new无法分配足够内存时该被调用的函数。其返回值也是一个指针,指向set_new_handler被调用前正在执行的(但马上就要被替换)的那个new-handler函数。
set_new_handler用法例子:
#include<new> #include<iostream> using namespace std; void outOfMem(){ cerr << "Unable to satisfy request for memory "; abort(); } int main(){ set_new_handler(outOfMem); int* pBigDataArray = new int[0x7fffffff/4]; system("pause"); return 0; }
运行结果:
2、当operator new无法满足内存申请时,会不断调用new-handler函数,直到找到足够内存。一个设计良好的new-handler函数必须做以下事情:
a、让更多内存可被使用。一个做法是程序一开始就分配一大块内存,当new-handler第一次被调用,将它们释还给程序使用。
b、安装另一个new-handler。如果目前这个new-handler函数无法取得更多可用内存,则可以安装另外那个new-handler替换自己(只要调用set_new_handler)。
c、卸载new-handler,也就是将null指针传给set_new_handler。一旦没有安装任何new-handler,operator new会在内在分配失败时抛出异常。
d、捕捉bad_alloc(或派生自bad_alloc)的异常。
e、不返回,通常调用abort或exit。
3、C++并不支持类专属之new-handlers,但我们可以自己实现出这种行为。只需令每一个类提供自己的set_new_handler和operator new即可。set_new_handler使客户可以指定类专属的new-handler(就像标准的set_new_handler允许客户指定global new-handler),operator new则确保在分配类对象内存的过程中以类专属之new-handler替换global new-handler。
例子:
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: //声明一个类型为new_handler的静态成员函数,用以指向该类的new-handler static std::new_handler currentHandler; };
注意static成员必须在类外定义(除非是const且是int型),所以需要这么写:
std::new_handler Widget::currentHandler = 0;
set_new_handler函数将它获得的指针存储起来,然后返回先前存储的指针,这也是标准版set_new_handler的作为:
std::new_handler Widget::set_new_handler(std::new_handler p) throw() { std::new_handler oldHandler = currentHandler; currendHandler = p; return oldHandler; }
最后,Widget的operator new做以下事情:
a、调用标准set_new_handler,告诉该类的错误处理函数,这会将该类的new-handler安装为全局的new-handler。
b、调用全局的operator new,执行实际的内存分配。如果分配失败,全局的operatornew会调用该类的new-handler,因为那个函数刚刚被安装为全局的new-handler。如果全局的new-handler最终无法分配足够内存,会抛出bad_alloc异常,在此情况下该类的operator new必须恢复原来的全局new-handler,然后再传播该异常。为确保原来的new-handler总是能够被重新安装,该类将全局new-handler视为“资源”并使用资源管理对象来防止资源泄漏。
c、如果全局operator new能够分配足够的内存,会返回一个指针指向分配的地址。该类的析构函数会管理全局new-handler,它会自动将该类operator new被调用前的那个全局new-handler恢复回来。
4、构造一个资源处理类来操作new-handler,那里只有基础性RAII操作,在构造过程中获得资源,在析构过程中释放:
class NewHandlerHolder { public: explicit NewHandlerHolder(std::new_handler nh) : handler(nh) {} //取得目前的new-handler ~NewHandlerHolder() {//释放new-handler set_new_handler(handler); } private: std::new_handler handler; //记录下来 NewHandlerHolder(const NewHandlerHolder&); //阻止拷贝行为 NewHandlerHolder& operator= (const NewHandlerHolder&); };
这使得类Widge的operator new的实现相当简单:
void* Widget::operator new(std::size_t size) throw(std::bad_alloc){ NewHandlerHolder h(std::set_new_Handler(currentHandler)); //安装Widget的new-handler return ::operator new(size); //分配内存或抛出异常,恢复全局new-handler }
Widget的客户应该类似如下方式new-handler:
void outOfMem(); //声明内存分配失败时的处理函数 Widget::set_new_handler(outOfMem); //将上面的函数设定为Widget的new-handler函数 Widget* pw1 = new Widget; //如果内存分配失败,调用outOfMem() std::string* ps = new std::string;//如果内存分配失败,调用全局的new-handler函数(如果有的话) Widget::set_new_handler(0); //设定Widget专属的new-handler为空,失去专属版本 Widget* pw2 = new Widget; //如果内存分配失败,则抛出异常
5、可以将4中例子加以复用,建立mixin风格的基类,它允许派生类继承单一特定能力——本例中是“设定该类专属的new-handler“的能力,并把该基类抽象为模板类。
template<typename T> class NewHandlerSupport { 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; }; template<typename T> std::new_handler NewHandlerHolder<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)); return ::operator new(size); }
以下是将每一个currentHandler初始化为null:
template<typename T> std::new_handler NewHandlerSupport<T>::currentHandler = 0;
有了上述模板类,为Widget添加添加专属的set_new_handler就轻而易举了:只要令Widget继承自NewHandlerSupport<Widget>就行。
class Widget : public NewHandlerSupport<Widget>{ ... //和先前一样,但不必声明set_new_handler或operator new };
6、因为之前的operator new在无法分配足够内存时会返回null。为了兼容,提供了另一种形式的operator new, 负责应传统的“分配失败时返回null”的行为,称为nothrow形式,因为他们在new的使用场合使用了nothrow对象。如下:
class Widget { ... }; Widget* pw1 = new Widget; //如果分配失败抛出bad_alloc if (pw1 == 0) ... //这个测试一定失败 Widget* pw2 = new (std::nothrow) Widget; // 如果分配失败,返回0 if (pw2 == 0) ... //这个测试可能成功
Nothrow new对异常的强制保证性并不高。
请记住:
- set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
- Nothrow new是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常。
版权声明:本文为博主原创文章,未经博主允许不得转载。