menu

hjk41的日志

Avatar

Effective C++ 笔记7: 对内存不足的处理

用 new 申请内存时有可能出现内存不足,从而导致申请失败的情况,这时就要采取相应的对策。大多数初学者都会觉得这没什么,毕竟有了虚拟内存技术,一般操作系统的可用内存都会在2G以上,不太可能出现内在不足,但这种可能毕竟是存在的,而一个好的程序就应该把所有可能存在的 bug 都去掉。

早期的C++标准要求 new 在申请失败的时候返回空指针,但现在的标准则使用 exception ,在申请失败的时候抛出一个 std::bad_alloc 类型的 exception。所以要对内存处理进行处理可以这么写:

try{
    int * n=new int[100000000000];
}
catch(std::bad_alloc &){
    cerr<<"not enough memory!"<<endl;
    exit(1);
}

但是在代码中对每个 new 都进行 try 和 catch 显然不是最好的方法。C++给我们提供了另一种方法,即 out-of-memory-handling-function 。在 new 函数出现错误时,它会先调用一个错误处理函数,然后再把 std::bad-alloc 抛出来。而C++允许我们自己通过 set_new_handler() 设置这个错误处理函数,这个函数的原型为:

typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();


这里,new_handler是一个函数指针,它是一个无参数无返回值的函数指针,指向我们自己定义的错误处理函数。set_new_handler 函数接受一个新的错误处理函数的指针,并返回原来的函数指针。比如我们可以这么写:

// function to call if operator new can't allocate enough memory
void noMoreMemory()
{
  cerr << "Unable to satisfy request for memory\n";
  abort();
}
int main()
{
  new_handler oldHandler=set_new_handler(noMoreMemory);
  int *pBigDataArray = new int[100000000];
  ...
  set_new_handler(oldHandler);
  ...
}


另外要强调的一点是,new 函数会重复调用错误处理函数,直到申请内在的操作成功,或是错误处理函数调用 abort() 或 exit()。

一般的错误处理函数可以做以下工作:
清理内存,以使下一次 new 能顺利进行。常见的做法是在程序开始时就申请一大块内存,然后在错误处理函数中把这块内在释放掉。
设置其它的错误处理函数。如果错误处理函数自己没办法释放更多内存了,那它可以调用 set_new_handler ,把控制权交给另一个错误处理函数。比如不能再释放内存时,可以把处理函数设成上面的 noMoreMemory,这样下一次错误处理时就会结束 new 操作。 
把错误处理函数指针设成空,这样当 new 发现指针为空时就会抛出异常并结束操作。
抛出异常,这样也会结束 new 操作。
调用 abort 或 exit,abort 会结束 new 操作,而 exit 就会退出程序。

有时候我们希望对每一个类进行不同的处理,这时可以重载这个类的 new 操作,示例代码如下:

class X {
public:
  static new_handler set_new_handler(new_handler p);
  static void * operator new(size_t size);
private:
  static new_handler currentHandler;
};

new_handler X::set_new_handler(new_handler p)
{
  new_handler oldHandler = currentHandler;
  currentHandler = p;
  return oldHandler;
}

void * X::operator new(size_t size)
{
  new_handler globalHandler =                // install X's
    std::set_new_handler(currentHandler);    // handler
  void *memory;
  try {                                      // attempt
    memory = ::operator new(size);           // allocation
  }
  catch (std::bad_alloc&) {                  // restore
    std::set_new_handler(globalHandler);     // handler;
    throw;                                   // propagate
  }                                          // exception

  std::set_new_handler(globalHandler);       // restore
                                             // handler
  return memory;
}

这么使用:

void noMoreMemory();                           // decl. of function to
                                               // call if memory allocation
                                               // for X objects fails
X::set_new_handler(noMoreMemory);
                                               // set noMoreMemory as X's
                                               // new-handling function
X *px1 = new X;                                // if memory allocation
                                               // fails, call noMoreMemory
string *ps = new string;                       // if memory allocation
                                               // fails, call the global
                                               // new-handling function
                                               // (if there is one)
X::set_new_handler(0);                         // set the X-specific
                                               // new-handling function
                                               // to nothing (i.e., null)
X *px2 = new X;                                // if memory allocation
                                               // fails, throw an exception
                                               // immediately. (There is
                                               // no new-handling function
                                               // for class X.)

上面的代码显然应该复用,所以我们这么写:

template<class T>				// "mixin-style" base class
class NewHandlerSupport {			// for class-specific
public:						// set_new_handler support
  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;


class X: public NewHandlerSupport<X> {
  ...                 // as before, but no declarations for
};                    // set_new_handler or operator new
class Y: public NewHandlerSupport<Y> {
  ...                 // as before, but no declarations for
};                    // set_new_handler or operator new


这里使用模板类是因为 currentHandler 是一个静态成员,如果不用模板的话那么类 X 和类 Y 就会共用一个 currentHandler 。

typedef void (*new_handler)();new_handler set_new_handler(new_handler p) throw();


这里不是很懂.能讲讲吗?谢谢!

typedef void (*new_handler)();
定义new_handler指针的类型,这是函数指针的定义方式,相当于这样一个函数:
void handler();
然后new_handler是指向这个函数的指针

new_handler set_new_handler(new_handler p) throw();
这是声明set_new_handler函数,它的参数跟返回值都是new_handler指针类型的,在错误时会抛出一个异常

评论已关闭