C++箴言:领略new-handler的行为
副标题#e#
当 operator new 不能满意一个内存分派请求时,它抛出一个 exception(异常)。好久以前,他返回一个 null pointer(空指针),而一些较量老的编译器还在这样做。你依然能到达以前的目标(在必然水平上),可是我要到本文的最后再接头它。
在 operator new 因回应一个无法满意的内存请求而抛出一个 exception 之前,它先挪用一个可以由客户指定的被称为 new-handler 的 error-handling function(错误处理惩罚函数)。(这并不完全确切,operator new 真正做的工作比这个稍微巨大一些,具体细节将在下一篇文章中接头。)为了指定 out-of-memory-handling function,客户挪用 set_new_handler ——一个在 <new> 中声明的尺度库函数:
namespace std {
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
}
就像你可以或许看到的,new_handler 是一个指针的 typedef,这个指针指向不取得和返回任何对象的函数,而 set_new_handler 是一个取得和返回一个 new_handler 的函数。(set_new_handler 的声明的末了处的 "throw()" 是一个 exception specification(异通例范)。它根基上是说这个函数不会抛出任何异常,尽量真相更有趣一些。关于细节,拜见《C++箴言:争取异常安详的代码》。)
set_new_handler 的形参是一个指向函数的指针,这个函数是 operator new 无法分派被请求的内存时应该挪用的。set_new_handler 的返回值是一个指向函数的指针,这个函数是 set_new_handler 被挪用前有效的方针。
你可以像这样利用 set_new_handler:
// function to call if operator new can't allocate enough memory
void outOfMem()
{
std::cerr << "Unable to satisfy request for memory\n";
std::abort();
}
int main()
{
std::set_new_handler(outOfMem);
int *pBigDataArray = new int[100000000L];
...
}
假如 operator new 不能为 100,000,000 个整数分派空间,outOfMem 将被挪用,而措施将在发出一个错误信息后中止。(顺便说一句,思量假如在写这个错误信息到 cerr… 的进程中内存必需被动态分派会产生什么。)
#p#副标题#e#
当 operator new 不能满意一个内存请求时,它重复挪用 new-handler function 直到它能找到足够的内存。可是从这种高条理的描写已足够推导出一个设计得好的 new-handler function 必需做到以下工作之一:
·Make more memory available(使得更多的内存可用)。这大概使得 operator new 中下一次内存分派的实验乐成。实现这一计策的一个要领是在措施启动时分派一大块内存,然后在 new-handler 第一次被挪用时释放它供措施利用。
·Install a different new-handler(安装一个差异的 new-handler)。假如当前的 new-handler 不能做到使更多的内存可用,或者它知道有一个差异的 new-handler 可以做到。假如是这样,当前的 new-handler 能在它本身的位置上安装另一个 new-handler(通过挪用 set_new_handler)。operator new 下一次挪用 new-handler function 时,它会获得最近安装的那一个。(这个主线上的一个变革是让一个 new-handler 改变它本身的行为,这样,下一次它被挪用时,可以做一些差异的工作。做到这一点的一个要领是让 new-handler 改变能影响 new-handler 行为的 static(静态),namespace-specific(名字空间专用)或 global(全局)的数据。)
·Deinstall the new-handler(卸载 new-handler),也就是,将空指针传给 set_new_handler。没有 new-handler 被安装,当内存分派没有乐成时,operator new 抛出一个异常。
·Throw an exception(抛出一个异常),范例为 bad_alloc 或担任自 bad_alloc 的其它范例。这样的异常不会被 operator new 捕捉,所以它们将被流传到发出内存请求的处所。
·Not return(不再返回),典范环境下,挪用 abort 或 exit。
这些选择使你在实现 new-handler functions 时拥有极大的弹性。
有时你大概但愿按照被分派 object 的差异,用差异的要领处理惩罚内存分派的失败:
class X {
public:
static void outOfMemory();
...
};
class Y {
public:
static void outOfMemory();
...
};
X* p1 = new X; // if allocation is unsuccessful,
// call X::outOfMemory
Y* p2 = new Y; // if allocation is unsuccessful,
// call Y::outOfMemory
C++ 没有对 class-specific new-handlers 的支持,可是它也不需要。你可以本身实现这一行为。你只要让每一个 class 提供 set_new_handler 和 operator new 的它本身的版本即可。class 的 set_new_handler 答允客户为这个 class 指定 new-handler(正像standard set_new_handler 答允客户指定global new-handler)。class 的 operator new 确保当为 class objects 分派内存时,class-specific new-handler 取代 global new-handler 被利用。
#p#分页标题#e#
假设你要为 Widget class 处理惩罚内存分派失败。你就必需清楚当 operator new 不能为一个 Widget object 分派足够的内存时所挪用的函数,所以你需要声明一个 new_handler 范例的 static member(静态成员)指向这个 class 的 new-handler function。Widget 看起来就像这样:
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 members(静态类成员)必需在 class 界说外被界说(除非它们是 const 并且是 integral),所以:
std::new_handler Widget::currentHandler = 0; // init to null in the class
// impl. file
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 将做下面这些工作:
以 Widget 的 error-handling function 为参数挪用 standard set_new_handler。这样将 Widget 的new-handler 安装为 global new-handler。
挪用 global operator new 举办真正的内存分派。假如分派失败,global operator new 挪用 Widget 的 new-handler,因为谁人函数适才被安装为 global new-handler。假如 global operator new 最后照旧无法分派内存,它会抛出一个 bad_alloc exception。在此环境下,Widget 的 operator new 必需规复本来的 global new-handler,然后流传谁人 exception。为了确保本来的 new-handler 总能被规复,Widget 将 global new-handler 作为一种资源看待,并遵循《C++箴言:利用工具打点资源》中的发起,利用 resource-managing objects(资源打点工具)来防范 resource leaks(资源泄漏)。
假如 global operator new 可以或许为一个 Widget object 分派足够的内存,Widget 的 operator new 返回一个指向被分派内存的指针。object 的用于打点 global new-handler 的 destructor(析构函数)自动将 global new-handler 规复到挪用 Widget 的 operator new 之前的状态。
以下就是你如安在 C++ 中表达这所有的工作。我们以 resource-handling class 开始,构成部门中除了根基的 RAII 操纵(在结构进程中得到资源并在析构进程中释放)(《C++箴言:利用工具打点资源》),没有更多的对象:
class NewHandlerHolder {
public:
explicit NewHandlerHolder(std::new_handler nh) // acquire current
:handler(nh) {} // new-handler
~NewHandlerHolder() // release it
{ std::set_new_handler(handler); }
private:
std::new_handler handler; // remember it
NewHandlerHolder(const NewHandlerHolder&); // prevent copying
NewHandlerHolder& // (see 《C++箴言:审慎思量资源打点类的拷贝行为》)
operator=(const NewHandlerHolder&);
};
这使得 Widget 的 operator new 的实现很是简朴:
void * Widget::operator new(std::size_t size) throw(std::bad_alloc)
{
NewHandlerHolder // install Widget's
h(std::set_new_handler(currentHandler)); // new-handler
return ::operator new(size); // allocate memory
// or throw
} // restore global
// new-handler
Widget 的客户像这样利用它的 new-handling capabilities(处理惩罚 new 的本领):
void outOfMem(); // decl. of func. to call if mem. alloc.
// for Widget objects fails
Widget::set_new_handler(outOfMem); // set outOfMem as Widget's
// new-handling function
Widget *pw1 = new Widget; // if memory allocation
// fails, call outOfMem
std::string *ps = new std::string; // if memory allocation fails,
// call the global new-handling
// function (if there is one)
Widget::set_new_handler(0); // set the Widget-specific
// new-handling function to
// nothing (i.e., null)
Widget *pw2 = new Widget; // if mem. alloc. fails, throw an
// exception immediately. (There is
// no new- handling function for
// class Widget.)
无论 class 是什么,实现这个方案的代码都是一样的,所以在其它处所重用它就是一个公道的方针。使它成为大概的一个简朴要领是建设一个 "mixin-style" base class(“殽杂气势气魄”基类),也就是说,一个设计为答允 derived classes(派生类)担任一个单一特定本领(在当前环境下,就是设定一个 class-specific new-handler 的本领)的 base class(基类)。然后把这个 base class(基类)转化为一个 template(模板),以便于你获得针对每一个 inheriting class(担任来的类)的 class data 的差异拷贝。
#p#分页标题#e#
这个设计的 base class(基类)部门让 derived classes(派生类)担任它们全都需要的 set_new_handler 和 operator new functions,而这个设计 template(模板)部门确保每一个 inheriting class(担任来的类)获得一个差异的 currentHandler data member(数据成员)。这听起来大概有点巨大,可是代码看上去靠得住并且熟悉。实际上,仅有的真正差异是它此刻可以用在任何需要它的 class 之上:
template<typename T> // "mixin-style" base class for
class NewHandlerSupport{
// class-specific set_new_handler
public: // support
static std::new_handler set_new_handler(std::new_handler p) throw();
static void * operator new(std::size_t size) throw(std::bad_alloc);
... // other versions of op. new
private:
static std::new_handler currentHandler;
};
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));
return ::operator new(size);
}
// this initializes each currentHandler to null
template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;
有了这个 class template(类模板),为 Widget 增加 set_new_handler 支持就很容易了:Widget 只需要从 NewHandlerSupport<Widget> 担任即可。(大概看起来很怪异,可是下面我将表明更多的细节。)
class Widget: public NewHandlerSupport<Widget> {
... // as before, but without declarations for
}; // set_new_handler or operator new
这些就是 Widget 为了提供一个 class-specific set_new_handler 所需要做的全部。
可是也许你依然在为 Widget 从 NewHandlerSupport<Widget> 担任而烦恼。假如是这样,当你留意到 NewHandlerSupport template 从来没有用到它的 type parameter T 时,你大概会越发烦恼。它不需要那样做。我们需要的全部就是为每一个从 NewHandlerSupport 担任的 class 提供一份差异的 NewHandlerSupport ——出格是它的 static data member(静态数据成员)currentHandler ——的拷贝。template parameter T 只是为了将一个 inheriting class 同另一个区分隔来。template 机制本身自动地为每一个被实例化的 NewHandlerSupport 中的 T 生成一个 currentHandler 的拷贝。
对付 Widget 从一个把 Widget 看成一个 type parameter(范例参数)的 templatized base class(模板化基类)担任,假如这个观念把你弄得有点糊涂,不必难熬。它最开始对每一小我私家都有这种影响。然而,它成长成如此有用的一项技能,它有一个名字,固然它正常看上去所反应的事实并不是他们第一次看到它的样子。它被称作 curiously recurring template pattern(怪异的递归模板模式) (CRTP)。真的。
在这一点上,我颁发了一篇文章发起一个更好的名字叫做 "Do It For Me",因为当 Widget 从 NewHandlerSupport<Widget> 担任时,它其实是在说:“我是 Widget,而我要从针对 Widget 的 NewHandlerSupport class 担任。”没有人利用我提议的名字(甚至是我本身),可是把 CRTP 思量成说 "do it for me" 的一种方法也许会辅佐你领略 templatized inheritance(模板化担任)在做些什么。