理解C++ new-handler机制
当 operator new 不能满足一个内存分配请求时,它抛出一个 exception(异常)。很久以前,他返回一个 null pointer(空指针),而一些比较老的编译器还在这样做。你依然能达到以前的目的(在一定程度上),但是我要到本文的最后再讨论它。? 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++箴言:争取异常安全的代码》。) // function to call if operator new can't allocate enough memory
void outOfMem()
{
std::cerr << "Unable to satisfy request for memoryn";
std::abort();
}
int main()
{
std::set_new_handler(outOfMem);
int *pBigDataArray = new int[100000000L];
...
}
如果 operator new 不能为 100,000,000 个整数分配空间,outOfMem 将被调用,而程序将在发出一个错误信息后中止。(顺便说一句,考虑如果在写这个错误信息到 cerr... 的过程中内存必须被动态分配会发生什么。) 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 被使用。 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。? 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 的不同拷贝。 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 所需要做的全部。 class Widget { ... };
Widget *pw1 = new Widget; // throws bad_alloc if
// allocation fails
if (pw1 == 0) ... // this test must fail
Widget *pw2 =new (std::nothrow) Widget; // returns 0 if allocation for
// the Widget fails
if (pw2 == 0) ... // this test may succeed
对于异常,nothrow new 提供了比最初看上去更少的强制保证。在表达式 "new (std::nothrow) Widget" 中,发生了两件事。首先,operator new 的 nothrow 版本被调用来为一个 Widget object 分配足够的内存。如果这个分配失败,众所周知,operator new 返回 null pointer。然而,如果它成功了,Widget constructor 被调用,而在此刻,所有打的赌都失效了。Widget constructor 能做任何它想做的事。它可能自己 new 出来一些内存,而如果它这样做了,它并没有被强迫使用 nothrow new。那么,虽然在 "new (std::nothrow) Widget" 中调用的 operator new 不会抛出,Widget constructor 却可以。如果它这样做了,exception 像往常一样被传播。结论?使用 nothrow new 只能保证 operator new 不会抛出,不能保证一个像 "new (std::nothrow) Widget" 这样的表达式绝不会导致一个 exception。在所有的可能性中,你最好绝不需要 nothrow new。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |