条款49中关于new-handler行为

来源:互联网 发布:域名到期如何续费 编辑:程序博客网 时间:2024/04/30 01:21

当operator new无法满足某一内存分配时,就会抛出一次。以前它会返回NULL指针,现在某些旧式编译器也还这么做。

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

new_handler是个函数指针,该函数没有参数也不返回任何东西。set_new_handler是设置一个new_handler并返回一个new_handler函数,返回的new_handler是指向set_new_handler被调用前正在执行的那个new-handler函数。后面的throw是一份异常明细,表示该函数不抛出异常。可以这样使用


#include <iostream>using namespace std;void outOfMem(){std::cerr << "Unable to satisfy request for memory\n";std::abort();}int main(){std::set_new_handler(outOfMem);int *pBigDataArray1 = new int[500000000L];int *pBigDataArray2 = new int[500000000L];int *pBigDataArray3 = new int[500000000L];int *pBigDataArray4 = new int[500000000L];return 0;}
设计良好的new-handler必须做好以下事情:

  • 让更多内存可被使用。这样可以造成operator new内的下一次内存分配动作可能成功。一个做法是,程序一开始就分配一大块内存,当new-handler第一次被调用时将它释放。
  • 安装另一个new-handler。当前的new-handler无法取得更多内存时,或许它直到哪个new-handler有此能力。
  • 卸除new-handler。即将null指针传给set_new_handler,一旦没有安装任何new-handler,operator new在内存分配不成功时便抛出异常。
  • 抛出bad_alloc(或派生自bad_alloc)的异常。这样的异常不会被operator new捕捉,因此不会被传播到内存索求处。
  • 不返回。通常abort或exit。

有时候,我们希望处理内存分配失败的情况和class相关。例如

class X{    public:        static void outOfMemory();        ……    };    class Y{    public:        static void outOfMemory();        ……    };    X* p1=new X;//分配不成功,调用X::outOfMemory    Y* p2=new Y;//分配不成功,调用Y::outOfMemory

C++并不支持class专属的new-handler,但是我们自己可以实现这种行为。令每一个class提供自己的set_new_handler和operator new即可。……………………………………………………………………………………

现在打算处理Widget class内存分配失败的情况。首先要有一个operator new无法为Widget分配足够内存时的调用函数,即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;    };    std::new_handler Widget::currentHandler=0;    std::new_handler Widget::set_new_handler(std::new_handler p) throw()    {        std::new_handler oldHandler=currentHandler;        currentHandler=p;        reutrn oldHandler;    }

Widget的operator new做以下事情: 
1、调用标准set_new_handler,告知Widget错误处理函数。这会将Widget的new-handler安装为global new-handler。 
2、调用global operator new,如果失败,global operator new会调用Widget的new-handler,因为第一步。如果global operator new最终无法分配足够内存,会抛出一个bad_alloc异常。这时Widget的operator new要恢复原本的global new-handler,之后在传播异常。 
3、如果global operator new调用成功,Widget的operator new会返回一个指针,指向分配的内存。Widget析构函数会管理global new-handler,它会将Widget’s operator new被调用前的那个global new-handler恢复回来。

class NewHandlerHolder{    public:        explicit NewHandlerHolder(std::new_handler nh)        :handlere(nh){}        ~NewHandlerHolder()        { std::set_new_handler(handler); }    private:        std::new_handler handler;        NewHandlerHolder&(const NewHandlerHolder&);//防止copying        NewHandlerHolder& operator-(const NewHandlerHolder&);    };

这使得Widget’s 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);    }

Widget客户应该类似这样使用其new-handling

void outOfMem();    Widget::set_new_handler(outOfMem);//设定outOfmem为Widget的new-handling函数    Widget* pw1=new Widget;//内存分配失败,则调用outOfMEM    std::string* ps=new std::string;//内存分配失败则调用global new-handling(如果有)    Widget::set_new_handler(0);//设定Widget专属new-handling为null    Widget* pw2=new Widget;//内存分配失败则立刻抛出异常

实现这个方案的class代码基本相同,用个基类base class加以复用是个好的方法。可以用个template base class,如此以来每个derived class将获得实体互异的class data复件。这个base class让其derived class继承它获取set_new_handler和operator new,template部分确保每一个derived class获得一个实体互异的currentHandler成员变量。

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    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);    }    //将每一个currentHandler初始化为null    template<typename T>    std::new_handler NewHandlerSupport<T>::currentHandler=0;有了这个class template,为Widget添加set_new_handler就容易了    class Widget:public NewHandlerSupport<Widget>{    ……    };

在template base class中,从未使用类型T。因为currentHandler是static类型,使用模板的话会是每个class都有自己的currentHandler。如果使用多重继承,要注意**条款**40所提到的内容。

C++中operator new分配失败抛出异常bad_alloc,但是旧标准是返回null指针。旧标准这个形式为nothrow形式。

class Widget{};    Widget* pw1=new Widget;//分配失败,抛出bad_alloc    if(pw1==null)//判断是否分配成功。但是这个测试失败    Widget* pw2=new(std::nothrow)Widget;//分配失败,返回null    if(pw2==null)//可以侦测

new(std::nothrow) Widget发生两件事,第一分配内存给Widget对象,如果失败返回null指针。第二,如果成功,调用Widget的构造函数,但是在这个构造函数做什么,nothrow new并不知情,有可能再次开辟内存。如果在构造函数使用operator new开辟内存,那么还是有可能抛出异常并传播。使用nothrow new只能保证operator new不抛出异常,不能保证像new(std::nothrow) Widget这样的表达式不抛出异常。所以,并没有运用nothrow的需要。

总结

  • set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
  • nothrow new是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是有可能抛出异常。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 产后四个月掉头发怎么办 洗头时严重掉发怎么办 头发老是掉得到处都是怎么办 短发洗完头头发向外外怎么办 20岁掉头发厉害怎么办 20岁老掉头发怎么办 20岁有点掉头发怎么办 20岁开始掉头发怎么办 20岁掉头发严重怎么办 20岁脱发很严重怎么办 手的纹路很深怎么办 20岁白头发很多怎么办 一天掉40根头发怎么办 烫完头发掉头皮怎么办 接发遗留的胶水怎么办 头发又干又卷怎么办 每天掉很多头发怎么办掉头发 植过发15天手抓植发区了怎么办 洗头梳头老掉头发怎么办 掉头发特别特别严重怎么办 哺乳期掉头发特别严重怎么办 最近掉头发特别严重怎么办 近掉头发特别严重怎么办 50多岁脱发严重怎么办 2岁宝宝掉发严重怎么办 生孩子后掉头发严重怎么办 有16岁孩孑教吾听怎么办 生完孩子后脱发怎么办 学生掉头发很厉害怎么办 35岁开始掉头发怎么办 高三学生玩手机怎么办 高三学生不学习怎么办 17岁经常掉头发怎么办 出汗后头皮很痒怎么办 头发老是掉怎么办会不会长出来 头发痒还掉头发怎么办 7个月宝宝入睡难怎么办 45天宝宝入睡难怎么办 两岁宝宝入睡难怎么办 吃激素掉发严重怎么办 20多岁掉头发怎么办