📄 8610.htm
字号:
<HTML>
<HEAD>
<meta http-equiv='Content-Type' content='text/html; charset=gb2312'>
<style >
.fst{padding:0px 15px;width:770px;border-left:0px solid #000000;border-right:0px solid #000000}
.fstdiv3 img{border:0px;border-right:8px solid #eeeecc;border-top:6px solid #eeeecc}
</style>
<title>
Effective C++ 2e Item7
</title>
</HEAD>
<BODY >
<center>
<div align=center><div class=fst align=left><div class=fstdiv3 id=print2>
<b>
Effective C++ 2e Item7</b><p>条款7:预先准备好内存不够的情况</p>
<p>operator new在无法完成内存分配请求时会抛出异常(以前的做法一般是返回0,一些旧一点的编译器还这么做。你愿意的话也可以把你的编译器设置成这样。关于这个话题我将推迟到本条款的结尾处讨论)。大家都知道,处理内存不够所产生的异常真可以算得上是个道德上的行为,但实际做起来又会象刀架在脖子上那样痛苦。所以,你有时会不去管它,也许一直没去管它。但你心里一定还是深深地隐藏着一种罪恶感:万一new真的产生了异常怎么办?</p>
<p>你会很自然地想到处理这种情况的一种方法,即回到以前的老路上去,使用预处理。例如,c的一种常用的做法是,定义一个类型无关的宏来分配内存并检查分配是否成功。对于c++来说,这个宏看起来可能象这样:<br> #define new(ptr, type) \<br> try { (ptr) = new type; } \<br> catch (std::bad_alloc&) { assert(0); }<br>(“慢!std::bad_alloc是做什么的?”你会问。bad_alloc是operator new不能满足内存分配请求时抛出的异常类型,std是bad_alloc所在的名字空间(见条款28)的名称。“好!”你会继续问,“assert又有什么用?”如果你看看标准c头文件<assert.h>(或与它相等价的用到了名字空间的版本<cassert>,见条款49),就会发现assert是个宏。这个宏检查传给它的表达式是否非零,如果不是非零值,就会发出一条出错信息并调用abort。assert只是在没定义标准宏ndebug的时候,即在调试状态下才这么做。在产品发布状态下,即定义了ndebug的时候,assert什么也不做,相当于一条空语句。所以你只能在调试时才能检查断言(assertion))。</p>
<p>new宏不但有着上面所说的通病,即用assert去检查可能发生在已发布程序里的状态(然而任何时候都可能发生内存不够的情况),同时,它还在c++里有另外一个缺陷:它没有考虑到new有各种各样的使用方式。例如,想创建类型t对象,一般有三种常见的语法形式,你必须对每种形式可能产生的异常都要进行处理:<br> new t;</p>
<p> new t(constructor arguments);</p>
<p> new t[size];</p>
<p>这里对问题大大进行了简化,因为有人还会自定义(重载)operator new,所以程序里会包含任意个使用new的语法形式。</p>
<p>那么,怎么办?如果想用一个很简单的出错处理方法,可以这么做:当内存分配请求不能满足时,调用你预先指定的一个出错处理函数。这个方法基于一个常规,即当operator new不能满足请求时,会在抛出异常之前调用客户指定的一个出错处理函数——一般称为new-handler函数。(operator new实际工作起来要复杂一些,详见条款8)</p>
<p>指定出错处理函数时要用到set_new_handler函数,它在头文件<new>里大致是象下面这样定义的:<br> typedef void (*new_handler)();<br> new_handler set_new_handler(new_handler p) throw();</p>
<p>可以看到,new_handler是一个自定义的函数指针类型,它指向一个没有输入参数也没有返回值的函数。set_new_handler则是一个输入并返回new_handler类型的函数。</p>
<p>set_new_handler的输入参数是operator new分配内存失败时要调用的出错处理函数的指针,返回值是set_new_handler没调用之前就已经在起作用的旧的出错处理函数的指针。</p>
<p>可以象下面这样使用set_new_handler:<br>// function to call if operator new can't allocate enough memory<br>void nomorememory()<br>{<br> cerr << "unable to satisfy request for memory\n";<br> abort();<br>}</p>
<p>int main()<br>{<br> set_new_handler(nomorememory);</p>
<p> int *pbigdataarray = new int[100000000];</p>
<p> ...</p>
<p>}</p>
<p>假如operator new不能为100,000,000个整数分配空间,nomorememory将会被调用,程序发出一条出错信息后终止。这就比简单地让系统内核产生错误信息来结束程序要好。(顺便考虑一下,假如cerr在写错误信息的过程中要动态分配内存,那将会发生什么...)</p>
<p>operator new不能满足内存分配请求时,new-handler函数不只调用一次,而是不断重复,直至找到足够的内存。实现重复调用的代码在条款8里可以看到,这里我用描述性的的语言来说明:一个设计得好的new-handler函数必须实现下面功能中的一种。<br> ·产生更多的可用内存。这将使operator new下一次分配内存的尝试有可能获得成功。实施这一策略的一个方法是:在程序启动时分配一个大的内存块,然后在第一次调用new-handler时释放。释放时伴随着一些对用户的警告信息,如内存数量太少,下次请求可能会失败,除非又有更多的可用空间。<br> ·安装另一个不同的new-handler函数。如果当前的new-handler函数不能产生更多的可用内存,可能它会知道另一个new-handler函数可以提供更多的资源。这样的话,当前的new-handler可以安装另一个new-handler来取代它(通过调用set_new_handler)。下一次operator new调用new-handler时,会使用最近安装的那个。(这一策略的另一个变通办法是让new-handler可以改变它自己的运行行为,那么下次调用时,它将做不同的事。方法是使new-handler可以修改那些影响它自身行为的静态或全局数据。)<br> ·卸除new-handler。也就是传递空指针给set_new_handler。没有安装new-handler,operator new分配内存不成功时就会抛出一个标准的std::bad_alloc类型的异常。<br> ·抛出std::bad_alloc或从std::bad_alloc继承的其他类型的异常。这样的异常不会被operator new捕捉,所以它们会被送到最初进行内存请求的地方。(抛出别的不同类型的异常会违反operator new异常规范。规范中的缺省行为是调用abort,所以new-handler要抛出一个异常时,一定要确信它是从std::bad_alloc继承来的。想更多地了解异常规范,参见条款m14。)<br> ·没有返回。典型做法是调用abort或exit。abort/exit可以在标准c库中找到(还有标准c++库,参见条款49)。</p>
<p>上面的选择给了你实现new-handler函数极大的灵活性。</p>
<p>处理内存分配失败的情况时采取什么方法,取决于要分配的对象的类:<br>class x {<br>public:<br> static void outofmemory();</p>
<p> ...</p>
<p>};</p>
<p>class y {<br>public:<br> static void outofmemory();</p>
<p> ...</p>
<p>};</p>
<p>x* p1 = new x; // 若分配成功,调用x::outofmemory</p>
<p>y* p2 = new y; // 若分配不成功,调用y::outofmemory</p>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -