Effective C++ 第二版 7) 内存不够的情况

56 篇文章 0 订阅

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值