条款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 memoryvoid 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::outOfMemoryY*
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; //
如内存分配失败,调用noMoreMemorystring
*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 0template<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_allocif (pw1
== 0) ... //
这个检查一定失败Widget
*pw2 = new (nothrow)
Widget; //
若分配失败返回0if (pw2
== 0) ... //
这个检查可能会成功 |
>无论用哪种形式, 重点是需要为内存分配失败做好准备;
---End---

本文深入探讨了在C++中处理内存分配失败的策略,包括使用宏、异常处理、自定义错误处理函数以及类级别的内存管理优化。详细介绍了如何在内存不足时调用特定的错误处理函数,以及如何利用`set_new_handler`和`operator new`来实现内存分配失败时的灵活处理。此外,文章还展示了如何通过模板和继承来创建可重用的代码,以简化不同类的内存管理过程。
1161

被折叠的 条评论
为什么被折叠?



