条款49:了解new-handler的行为
Understand the behavior of the new-handler.
在我们平时的编写代码过程中,总是避免不了去new出一些对象出来.我们知道new操作符私底下通过调用
operator new来实现内存分配的.当operator new抛出异常以反映一个未获满足的内存需求之前,它会调用一
个客户指定的错误处理函数,一个所谓的new-handler.而客户是通过set_new_handler将自己的new-handler传
递给它的,其声明在<new>标准库函数中:
namespace std{
typedef void (*new_handler)();
new_handler set_new_handler( new_handler p ) throw();
}
当operator new无法满足内存申请时,它会不断调用new-handler函数,直到找到足够内存.关于反复调用
代码我们在条款51中我们会讨论.一个设计良好的new-handler函数必须做以下事情:
■ 让更多内存可被使用.
■ 安装另一个new-handler.
■ 卸除new-handler
■ 抛出bad_alloc(或派生自bad_alloc)的异常.
■ 不返回.
这些选择让你在实现new-handler函数时拥有很大的弹性.而有时候你会希望'依据不同的类,调用附属
与该类的new-handler,看起来也许是这个样子:
struct TestA{
static void outOfMemory();
...
};
struct TestB{
static void outOfMemory();
...
};
TestA* a = new TestA;//if memory runs out,call TestA::outOfMemory
TestB* b = new TestB;//if memory runs out,call TestB::outOfMemory
但是C++不支持class专属之new-handlers,但你可以自己实现这种行为.好,我们来试试!
假设我们现在打算处理Widget class的内存分配失败情况,我们需要声明一个类型为new_handler的static
成员,用以指向class Widget的new-handler,看起来应该像这样:
struct Widget{
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 st_current_handler_;
};
Widget内的set_new_handler函数会将它获得的指针存储起来,然后返回调用之前的指针,这跟标准版
set_new_hander的行为是一样的:
std::new_handler Widget::set_new_handler( std::new_handler p ) throw (){
std::new_handler old_handler = st_current_handler_;
st_current_handler_ = p;
return old_handler;
}
下面我将构造一个资源处理类来操作new-handler,在构造过程中获得资源,在析构过程中释放:
struct NewHandlerHolder{
explicit NewHandlerHolder(std::new_handler handler)
:handler_( handler ){}
~NewHandlerHolder(){
std::set_new_handler( handler_ );
}
private:
NewHandlerHolder(const NewHandlerHolder&);
NewHandlerHolder& operator=(const NewHandlerHolder&);
std::new_handler handler_;
};
这样Widget内的operator new的实现就变的容易了:
void* Widget::operator new(std::size_t size) throw(std::bad_alloc)
{
NewHandlerHolder alloc_handler( std::set_new_handler( st_current_handler_ ) );
return ::operator new( size );
}
客户现在可以这样用:
void outOfMemory();
Widget::set_new_handler( outOfMemory );
Widget* new_widget_obj = new Widget; //if memory allocation failed,
//call outOfMemory
std::string* new_string = new std::string; //if memory allocation failed,
//call global new-handling function
//if exists.
Widget::set_new_handler( 0 );
Widget* another_widget = new Widget; // if memory allocation failed,throw
//exception at once.
其实我们可以用base class将new-handler操作独立出来,让Widget继承自base class,呵呵,这样是
不是更好:
template<typename T>
struct NewHandlerSupport{
static std::new_handler set_new_handler( std::new_handler p ) throw (){
std::new_handler old_handler = st_current_handler_;
st_current_handler_ = p;
return old_handler;
}
static void* operator new( std::size_t size ) throw ( std::bad_alloc ){
NewHandlerHolder h( std::set_new_handler( st_current_handler_ ) );
return ::operator new( size );
}
...
private:
static std::new_handler st_current_handler_;
};
template<typename T>
std::new_handler NewHandlerSupport<T>::st_current_handler_ = 0;
有了这个template,为Widget添加set_new_handler支持能力就轻而易举了:
struct Widget:public NewHandlerSupport<Widget>{
...
};
好了,这个问题的讨论,就结束了.我们来把目光转到Nothrow new.
nothrow版的operator new被调用,用以分配足够内存给Widget对象.如果分配失败便返回null指针,
一如文档所言.所有分配成功,接下来Widget构造函数会被调用,而在那一点上所有的筹码便被耗尽,因为
Widget构造函数可以做它想做的任何事情.它有可能又new一些内存,而没人可以强迫它再次使用nothrow
new.现在我们可以得出结论:使用nothrow new只能保证operaor new不抛掷异常,不保证像'new (std:
:nothrow) Widget'这样的表达式不导致异常,其实你没有运用nothrow new的需要.
今天的讨论就到这里,我们下款再见!
请记住:
■ set_new_handler允许客户指定一个函数,在内存分配无法获得满足时调用.
■ nothrow new是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能
抛出异常.
条款49:了解new-handler的行为
最新推荐文章于 2023-02-21 00:24:54 发布