条款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---