条款7 预先准备好内存不够的情况
operator new在无法完成内存分配请求时会抛出异常(老的编译器返回0);
C常用的做法, 定义一个类型无关的宏来分配内存并检查是否成功;
1
2
3
|
#define
NEW(PTR, TYPE) \ try {
(PTR) = new TYPE;
} \ catch (std::bad_alloc&)
{ assert (0);
} |
>bad_alloc是operator new不能满足内存分配请求时抛出的异常类型;
>assert是个宏, 检查表达式是否非零, 如果不是非零值, 就发出一条出错信息并调用abort.
Note assert只是在没有定义标准宏NDEBUG, 调试状态下才有用, 在release版本(定义了NDEBUG)中, 什么也不做;
NEW宏缺陷: 1) 会用assert去检查可能发生在release程序里的状态; 2) 没有考虑到new有各种使用方式;
new T; new T(construct arguments); new T[size]; 还有人可能自定义(重载)operator new, 程序会包含任意个使用new的语法形式;
简单处理方法: 当内存分配请求不能满足时, 调用预先指定的出错处理函数;
在抛出异常前调用客户指定的出错处理函数: new-handler函数;
指定出错处理函数: set_new_handler, 在<new>里的大致定义:
1
2
|
typedef void (*new_handler)(); new_handler
set_new_handler(new_handler p) throw (); |
>new_handler是自定义的函数指针, 指向一个没有输入参数也没有返回值的函数, set_new_handler输入并返回new_handler类型;
1
2
3
4
5
6
7
8
9
10
11
|
//
function to call if operator new can't allocate enough memory void noMoreMemory() { cerr
<< "Unable
to satisfy request for memory\n" ; abort (); } int main() { set_new_handler(noMoreMemory); int *pBigDataArray
= new int [100000000]; //... |
>noMoreMemory()会在内存分配出错的时候被调用, 然后程序发出出错信息后终止;
在opertaor new不能满足内存分配请求时, new-handler函数会不断重复, 直到找到足够的内存;
一个设计的好的new-handler函数必须实现其中一种功能:
1) 产生更多的可用内存; 使operator new下一次分配内存的尝试可能成功;
在程序启动时分配一个大内存块, 在第一次调用new-handler时释放, 同时输出对用户的警告信息: 内存数量太少, 下次请求可能失败, 需要更多可用空间;
2) 安装一个不同的new-handler函数, 如果当前的new-handler不能产生更多可用内存, 可能另一个new-handler可以提供更多资源;
通过set_new_handler在下次operator new调用new-handler时使用新安装的handler; (或者让new-handler改变自己的行为, 下次调用时做不同的事情; 方法是让new-handler修改静态或全局数据)
3) 卸除new-handler: 传递空指针给set_new_handler, operator new就会抛出标准的bad_alloc类型的异常;
bad_alloc类型的异常不会被operator new捕捉, 他们会被送到最初进行内存请求的地方;
Note 抛出不同类项的异常会违反operator new的异常规范, 缺省行为是abort, 所以new-handler抛出异常时要确信是bad_alloc类型(or继承);
4) 没有返回, 调用abort或exit;
处理内存分配失败的时候采取什么方法, 取决于要分配的对象的类型;
1
2
3
4
5
6
7
8
9
10
11
12
|
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++不支持专门针对类的new-handler函数, 可以自己实现: 在每个类中提供自己版本的set_new_handler 和operator new; 类的operator new保证使用类的new-handler取代全局的handler;
假设处理类X的内存分配失败, operator new对X的对象分配内存失败时, 每次必须调用handler, 所以在类里声明static的handler成员;
1
2
3
4
5
6
7
|
class X
{ public : static new_handler
set_new_handler(new_handler p); static void *
operator new ( size_t size); private : static new_handler
currentHandler; } |
类外定义static成员:
1
|
new_handler
X::currentHandler; //
缺省设置currentHandler 为0(即null) |
set_new_handler会保存传给他的指针, 返回在调用之前保存的指针; (和标准版本一致)
1
2
3
4
5
6
|
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的new-handler. X的new-handler函数成为全局new-handler函数;
2) 调用全局operator new分配内存. 如果第一次分配失败, 全局operator new调用X的new-handler, 如果全局opertaor new最后未能分配到内存, 他抛出std::bad_alloc异常, X的operator new会捕捉到这个异常. X的operator new恢复最初被取代的全局new-handler函数, 最后抛出异常返回;
3) 假设全局operator new为X的对象分配内存成功, X的operator new会再次调用标准set_new_handler来恢复最初的全局处理函数. 最后返回分配成功的内存指针;
X类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
void *
X::operator new ( size_t size) { new_handler
globalHandler = //
安装X 的new_handler std::set_new_handler(currentHandler); void *memory; try { //
尝试分配内存 memory
= ::operator new (size); } catch (std::bad_alloc&)
{ //
恢复旧的new_handler std::set_new_handler(globalHandler); throw ; //
抛出异常 } std::set_new_handler(globalHandler); //
恢复旧的new_handler return memory; } |
使用类X的内存分配处理功能:
1
2
3
4
5
6
7
8
9
10
11
|
void noMoreMemory(); //
X 的对象分配内存失败时调用的new_handler 函数的声明 // X::set_new_handler(noMoreMemory); //把
noMoreMemory设置为X 的new-handling 函数 // X
*px1 = new X; //
如内存分配失败,调用noMoreMemory string
*ps = new string; //
如内存分配失败,调用全局new-handling 函数 // X::set_new_handler(0); //
设X 的new-handling 函数为空 // X
*px2 = new X; //
如内存分配失败,立即抛出异常(类X 没有new-handling 函数) // |
使用继承和模板来设计可重用代码; 创建一个混合风格mixin-style的基类;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
template < class T> //
提供类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; }; template < class T> new_handler
NewHandlerSupport<T>::set_new_handler(new_handler p) { new_handler
oldHandler = currentHandler; currentHandler
= p; return oldHandler; } template < class T> void *
NewHandlerSupport<T>::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 0 template < class T> new_handler
NewHandlerSupport<T>::currentHandler; |
>子类可继承set_new_handler和operator new功能, 模板使每个子类有不同的currentHandler成员;
使用起来很简单:
1
2
|
class X: public NewHandlerSupport<X>
{ //... }; |
使用X的时候不用修改代码, 依照之前的方式;
使用set_new_handler是处理内存不够的一种方便, 简单的方法. 比把每个new都包装在try模块中好;
NewHandlerSupport模板使得向任何类增加一个特定的new-handler更简单; 混合风格的继承需要注意多继承的情况;
1993年之前Cpp在内存分配失败时operator new返回0, 现在则抛出std::bad_alloc异常; C++委员会提供了另外形式的operator new/operator new[]继续提供返回0的功能;
这些形式被称为'无抛出', 没有throw, 而是在new的入口点采用了nothrow对象:
1
2
3
4
5
|
class Widget
{ ... }; Widget
*pw1 = new Widget; //
分配失败抛出std::bad_alloc if (pw1
== 0) ... //
这个检查一定失败 Widget
*pw2 = new ( nothrow )
Widget; //
若分配失败返回0 if (pw2
== 0) ... //
这个检查可能会成功 |
>无论用哪种形式, 重点是需要为内存分配失败做好准备;
---End---