new_handler

原文出处不详。。。。


new_handler


        当operator new无法满足某一内存分配需求时,会抛出异常。在抛出异常以反映一个未获满足的内存需求之前,它会先调用客户指定的错误处理函数,new_handler。为了指定这个“用以处理内存不足”的函数,客户必须调用set_new_handler,那是声明于<new>的一个标准函数库函数:

namespace std{
    typedef void (*new_handler)();
    new_handler set_new_handler(new_handler p) throw();
}

thow()是一份异常明细表,表示函数不会抛出任何异常。

set_new_handler参数是一个指针指向operator new无法分配足够内存时该被调用的函数。返回指向set_new_handler被调用前正在执行的那个new_handler函数(也即将被替换的那个函数)。 

void outOfMem()
{
    std::cout <<"Unable to satisfy request for memory\n";
    std::abort();
}
int main()
{
    std::set_new_handler(outOfMem);
    int* pBigDataArray = newint[1000000000000L];
}

一个设计良好的new_handler必须做以下事情:

1.        让更多内存可使用:造成operator new内的下一次内存分配动作可能成功。一个做法是,程序开始执行时就分配一大块内存,在new_handler第一次被调用时,再将它们释还给程序使用。

2.        安装另一个new_handler。如果这个new_handler无法取得更多可用内存,或许它知道另外有个new_handler有此能力,目前这个就可以安装另外那个以替换自己(只需调用set_new_handler)。下次当operator new调用new_handler,调用的将是最新安装的那个。(这个旋律的变奏之一就是让new_handler修改自己的行为,于是当他下次被调用就会做些不同的事。为达此目的,做法之一就是令new_handler修改“会影响new_handler行为”的static数据、namespace数据、或global数据。)

3.        卸除new_handler。就是将null指针传给set_new_handler。一旦没有安装任何new_handleroperatornew会在内存分配不成功时抛出异常。

4.        抛出bad_alloc(或派生自bad_alloc)的异常。这样的异常不会被operator new捕获,因此会传到内存索求处。

5.        不返回。通常调用abort或exit。

 

有时你想以不同方式处理内存分配失败情况,你希望视被分配物属于哪个class而定:

c++并不支持class专属之new_handler,其实也不需要。你可以自己实现出这种行为。只需令每一个class提供自己的set_new_handleroperator new即可。其中set_new_handler使客户指定class专属的new_handler

现在,假设你打算处理Widgetclass 的内存分配失败情况。

class Widget{
public:
    static std::new_handler set_new_handler(std::new_handler p) throw();
   static void* operator new(std::size_tsize) throw(std::bad_alloc);
private:
   static std::new_handlercurrentHandler;
};

static成员必须在class定义之外定义(除非它们是const而且是整数型):

std::new_handlerWidget::currentHandler = 0;
std::new_handlerWidget::set_new_handler(std::new_handler p) throw()
{
   std::new_handler oldHandler =currentHandler;
   currentHandler = p;
   return oldHandler;
}

Widget的operatornew做以下事情:

1.调用标准set_new_handler,告知Widget的错误处理函数,这回将Widget的new_handler安装为globalnew_handler

2.调用global operator new,执行实际的内存分配。如果分配失败,globaloperator new会调用Widget的new_handler,才刚被安装为globalnew_handler。如果globaloperator new最终无法分配足够内存,会抛出一个bad_alloc异常。此情况下Widget的operator new必须恢复原本的globalnew_handler,然后再传播该异常。为确保原本的new_handler总是能够被重新安装回去,Widget将globalnew_handler视为资源并遵守条款13忠告,运用资源管理对象防止资源泄漏。

3.如果global operator new能够分配足够一个Widget对象所用的内存,Widget的operator new会返回一个指针,指向分配所得。Widget析构函数会管理globalnew_handler,它会自动将widget’soperator new被调用前的那个globalnew_handler恢复回来。 

class NewHandlerHolder{
public:
explicitNewHandlerHolder(std::new_handlernh) : handler(nh){}    
//取得目前的new_handler
   ~NewHandlerHolder()
    {
       std::set_new_handler(handler);                                    //释放它
    }
private:
   std::new_handlerhandler;                                            //记录下来
   NewHandlerHolder(const NewHandlerHolder&);            //阻止copying
   NewHandlerHolder& operator=(const NewHandlerHolder&);
};

这就使得Widget’s operator new的实现相当简单:

void* Widget::operatornew(std::size_t size) throw(std::bad_alloc)
{
NewHandlerHolderh(std::set_new_handler(currentHandler));
//安装 Widget的currentHandler,同时将global new_handler保存在h对象里,确保结束后原本的new_handler总是能够被重新安装回去
   return ::operator new(size);             //分配内存或抛出异常。
}                                                                                   //h释放,globalnew_handler得以恢复

Widget的客户应该这样使用其new_handling:

void outOfMem();
Widget::set_new_handler(outOfMem);
Widget* pw1 = new Widget;        //如果内存分配失败,调用outOfMem
std::string* ps = new std::string;     //如果内存分配失败,调用global new_handler函数
Widget::set_new_handler(0);
Widget* pw2 = new Widget;       //如果内存分配失败,立刻抛出异常,new_handler为null

实现这一方案的代码并不因class的不同而不同,因此在它处加以复用是个合理的构想。一个简单的做法是建立一个mixin风格的base class,这种base class允许derivedclasses继承单一特定能力。然后将这个base class转换为template,如此一来每个derived class将获得实体互异的class data复件。 

template<typename T>
class NewHandlerSupport{
public:
   static std::new_handler set_new_handler(std::new_handler p) throw();
   static void* operator new(std::size_tsize) throw(std::bad_alloc);
private:
   static std::new_handlercurrentHandler;
};
template<typename T>
std::new_handlerNewHandlerSupport<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)
{
   NewHandlerHolder h(std::set_new_handler(currentHandler));
   return ::operator new(size);
}

以下将每个currentHandler初始化为null

template<typename T>
std::new_handlerNewHandlerSupport<T>::currentHandler = 0;

为Widget添加set_new_handler支持能力就轻而易举了:只要Widget继承自NewHandlerSupport<Widget>就好。

class Widget: publicNewHandlerSupport<Widget>{
   ...                //和先前一样,但不必声明set_new_handler或operator new
};

实际上使用template T只是希望,继承自NewHandlerSupport的每个class,拥有实体互异的NewHandlerSupport复件(更确切的说是其static成员变量currentHandler)。类型参数只是用来区分不同的derived class。template机制会自动为每一个T(NewHandlerSupport赖以具现化的根据)生成一份currentHandler,确保每次调用结束后能够恢复原有的globalnew_handler

Widget继承自一个模板化的base class,而后者又以Widget作为类型参数:“怪异的循环模板模式”(curiously recurring template pattern;crtp)。像是do it forme.

 

1993年之前,operator new在无法分配足够内存时返回null。新一代的operator new 抛出 bad_alloc异常。 

class Widget{...};
Widget* pw1 = new Widget;
if (pw1 == 0)...      //这个测试一定失败,因为如果分配失败,抛出bad_alloc异常
Widget* pw2 = new(std::nothrow) Widget;
if (pw2 == 0)...      //这个测试可能成功,如果分配Widget失败,返回0

nothrow new是个颇为局限的东西,因为它只适用于内存分配,后续的构造函数还是可能抛出异常。如果构造函数又new一些东西,没人强迫它再次适用nothrow new

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值