预先预备好内存不够的情况,异常处理

来源:互联网 发布:淘宝互刷群 编辑:程序博客网 时间:2024/05/03 04:28
new宏不但有着上面所说的通病,即用assert去检查可能发生在已发布程序里的状态(然而任何时候都可能发生内存不够的情况),同时,它还在c ++里有另外一个缺陷:它没有考虑到new有各种各样的使用方式。例如,想创建类型t对象,一般有三种常见的语法形式,你必须对每种形式可能产生的异常都要进行处理:new t;new t(constrUCtor arguments);new t[size];这里对问题大大进行了简化,因为有人还会自定义(重载)operator new,所以程序里会包含任意个使用new的语法形式。那么,怎么办?假如想用一个很简单的出错处理方法,可以这么做:当内存分配请求不能满足时,调用你预先指定的一个出错处理函数。这个方法基于一个常规,即当operator new不能满足请求时,会在抛出异常之前调用客户指定的一个出错处理函数——一般称为new-handler函数。(operator new实际工作起来要复杂一些,详见条款8)指定出错处理函数时要用到set_new_handler函数,它在头文件里大致是象下面这样定义的:typedef void (*new_handler)();new_handler set_new_handler(new_handler p) throw();可以看到,new_handler是一个自定义的函数指针类型,它指向一个没有输入参数也没有返回值的函数。set_new_handler则是一个输入并返回new_handler类型的函数。set_new_handler的输入参数是operator new分配内存失败时要调用的出错处理函数的指针,返回值是set_new_handler没调用之前就已经在起作用的旧的出错处理函数的指针。可以象下面这样使用set_new_handler:// function to call if operator new can't allocate enough memoryvoid nomorememory(){cerr << "unable to satisfy request for memory";abort();}int main(){set_new_handler(nomorememory);int *pbigdataarray = new int[100000000];...}假如operator new不能为100,000,000个整数分配空间,nomorememory将会被调用,程序发出一条出错信息后终止。这就比简单地让系统内核产生错误信息来结束程序要好。(顺便考虑一下,假如cerr在写错误信息的过程中要动态分配内存,那将会发生什么...)operator new不能满足内存分配请求时,new-handler函数不只调用一次,而是不断重复,直至找到足够的内存。实现重复调用的代码在条款8里可以看到,这里我用描述性的的语言来说明:一个设计得好的new-handler函数必须实现下面功能中的一种。·产生更多的可用内存。这将使operator new下一次分配内存的尝试有可能获得成功。实施这一策略的一个方法是:在程序启动时分配一个大的内存块,然后在第一次调用new-handler时释放。释放时伴随着一些对用户的警告信息,如内存数量太少,下次请求可能会失败,除非又有更多的可用空间。·安装另一个不同的new-handler函数。假如当前的new-handler函数不能产生更多的可用内存,可能它会知道另一个new- handler函数可以提供更多的资源。这样的话,当前的new-handler可以安装另一个new-handler来取代它(通过调用 set_new_handler)。下一次operator new调用new-handler时,会使用最近安装的那个。(这一策略的另一个变通办法是让new-handler可以改变它自己的运行行为,那么下次调用时,它将做不同的事。方法是使new-handler可以修改那些影响它自身行为的静态或全局数据。)·卸除new-handler。也就是传递空指针给set_new_handler。没有安装new-handler,operator new分配内存不成功时就会抛出一个标准的std::bad_alloc类型的异常。·抛出std::bad_alloc或从std::bad_alloc继续的其他类型的异常。这样的异常不会被operator new捕捉,所以它们会被送到最初进行内存请求的地方。(抛出别的不同类型的异常会违反operator new异常规范。规范中的缺省行为是调用abort,所以new-handler要抛出一个异常时,一定要确信它是从std::bad_alloc继续来的。想更多地了解异常规范,参见条款m14。)·没有返回。典型做法是调用abort或exit。abort/exit可以在标准c库中找到(还有标准c++库,参见条款49)。上面的选择给了你实现new-handler函数极大的灵活性。处理内存分配失败的情况时采取什么方法,取决于要分配的对象的类:class x {public:static voidoutofmemory();...};class y {public:static void outofmemory();...};x* p1 = new x; // 若分配成功,调用x::outofmemoryy* p2 = new y; // 若分配不成功,调用y::outofmemoryc++不支持专门针对于类的new-handler函数,而且也不需要。你可以自己来实现它,只要在每个类中提供自己版本的set_new_handler和operator new。类的set_new_handler可以为类指定new-handler(就象标准的set_new_handler指定全局new-handler一样)。类的operator new则保证为类的对象分配内存时用类的new-handler取代全局new-handler。假设处理类x内存分配失败的情况。因为operator new对类型x的对象分配内存失败时,每次都必须调用出错处理函数,所以要在类里声明一个new_handler类型的静态成员。那么类x看起来会象这样:class x {public:static new_handler set_new_handler(new_handler p);static void * operator new(size_t size);PRivate:static new_handler currenthandler;};类的静态成员必须在类外定义。因为想借用静态对象的缺省初始化值0,所以定义x::currenthandler时没有去初始化。new_handler x::currenthandler; //缺省设置currenthandler为0(即null)类x中的set_new_handler函数会保存传给它的任何指针,并返回在调用它之前所保存的任何指针。这正是标准版本的set_new_handler所做的:new_handler x::set_new_handler(new_handler p){new_handler oldhandler = currenthandler;currenthandler = p;return oldhandler;}最后看看x的operator new所做的:1. 调用标准set_new_handler函数,输入参数为x的出错处理函数。这使得x的new-handler函数成为全局new-handler函数。注重下面的代码中,用了"::"符号显式地引用std空间(标准set_new_handler函数就存在于std空间)。2. 调用全局operator new分配内存。假如第一次分配失败,全局operator new会调用x的new-handler,因为它刚刚(见1.)被安装成为全局new-handler。假如全局operator new最终未能分配到内存,它抛出std::bad_alloc异常,x的operator new会捕捉到它。x的operator new然后恢复最初被取代的全局new-handler函数,最后以抛出异常返回。3. 假设全局operator new为类型x的对象分配内存成功,, x的operator new会再次调用标准set_new_handler来恢复最初的全局出错处理函数。最后返回分配成功的内存的指针。c++是这么做的:void * x::operator new(size_t size){new_handler globalhandler = // 安装x的new_handlerstd::set_new_handler(currenthandler);void *memory;try { // 尝试分配内存memory = ::operator new(size);}catch (std::bad_alloc&) { // 恢复旧的new_handlerstd::set_new_handler(globalhandler);throw; // 抛出异常}std::set_new_handler(globalhandler); // 恢复旧的new_handlerreturn memory;}假如你对上面重复调用std::set_new_handler看不顺眼,可以参见条款m9来除去它们。使用类x的内存分配处理功能时大致如下:void nomorememory();// x的对象分配内存失败时调用的new_handler函数的声明x::set_new_handler(nomorememory);// 把nomorememory设置为x的// new-handling函数x *px1 = new x;// 如内存分配失败,// 调用nomorememorystring *ps = new string;// 如内存分配失败,调用全局new-handling函数x::set_new_handler(0);// 设x的new-handling函数为空x *px2 = new x;// 如内存分配失败,立即抛出异常// (类x没有new-handling函数)你会注重到,处理以上类似情况,假如不考虑类的话,实现代码是一样的,这就很自然地想到在别的地方也能重用它们。正如条款41所说明的,继续和模板可以用来设计可重用代码。在这里,我们把两种方法结合起来使用,从而满足了你的要求。你只要创建一个“混合风格”(mixin-style)的基类,这种基类答应子类继续它某一特定的功能——这里指的是建立一个类的new-handler的功能。之所以设计一个基类,是为了让所有的子类可以继续set_new_handler和operator new功能,而设计模板是为了使每个子类有不同的currenthandler数据成员。这听起来很复杂,不过你会看到代码其实很熟悉。区别只不过是它现在可以被任何类重用了。template // 提供类set_new_handler支持的class newhandlersupport { // 混合风格”的基类public:static new_handler set_new_handler(new_handler p);static void * operator new(size_t size);private:static new_handler currenthandler;};templatenew_handler newhandlersupport::set_new_handler(new_handler p){new_handler oldhandler = currenthandler;currenthandler = p;return oldhandler;}templatevoid * newhandlersupport::operator new(size_t size){new_handler globalhandler =std::set_new_handler(currenthandler);void *memory;try {memory = ::operator new(size);}catch (std::bad_alloc&) {std::set_new_handler(globalhandler);throw;}std::set_new_handler(globalhandler);return memory;}// this sets each currenthandler to 0templatenew_handler newhandlersupport::currenthandler;有了这个模板类,对类x加上set_new_handler功能就很简单了:只要让x从newhandlersupport继续:// note inheritance from mixin base class template. (see// my article on counting objects for information on why// private inheritance might be preferable here.)class x: public newhandlersupport {... // as before, but no declarations for}; // set_new_handler or operator new使用x的时候依然不用理会它幕后在做些什么;老代码依然工作。这很好!那些你常不去理会的东西往往是最可信赖的。使用set_new_handler是处理内存不够情况下一种方便,简单的方法。这比把每个new都包装在try模块里当然好多了。而且, newhandlersupport这样的模板使得向任何类增加一个特定的new-handler变得更简单。“混合风格”的继续不可避免地将话题引入到多继续上去,在转到这个话题前,你一定要先阅读条款43。1993年前,c++一直要求在内存分配失败时operator new要返回0,现在则是要求operator new抛出std::bad_alloc异常。很多c++程序是在编译器开始支持新规范前写的。c++标准委员会不想放弃那些已有的遵循返回0规范的代码,所以他们提供了另外形式的operator new(以及operator new[]——见条款8)以继续提供返回0功能。这些形式被称为“无抛出”,因为他们没用过一个throw,而是在使用new的入口点采用了nothrow对象:class widget { ... };widget *pw1 = new widget;// 分配失败抛出std::bad_alloc ifif (pw1 == 0) ... // 这个检查一定失败widget *pw2 = new (nothrow) widget; // 若分配失败返回0if (pw2 == 0) ... // 这个检查可能会成功不管是用“正规”(即抛出异常)形式的new还是“无抛出”形式的new,重要的是你必须为内存分配失败做好预备。最简单的方法是使用set_new_handler,因为它对两种形式都有用资料引用:http://www.knowsky.com/395404.html
原创粉丝点击