本章开始讨论内存分配的一些用法,C/C++内存分配采用new和delete。在new申请内存时,可能会遇到的一种情况就是,内存不够了,这时候会抛出out of memory的异常。有的时候,我们希望能够调用自己定制的异常处理函数,这就是本条款要说的。
在声明于<new>的一个标准程序库中,有如下的接口:
1 namespace std 2 { 3 typedef void (*new_handler)(); 4 new_handler set_new_handler(new handler p) throw(); 5 }
注意这里面typedef了一个函数指针new_handler,它指向一个函数,这个函数的返回值为void,形参也是void。set_new_handler就是将new_handler指向具体的函数,在这个函数里面处理out of memory异常(函数末尾的throw()表示它不抛出任务异常),如果这个new_handler为空,那么这个函数没有执行,就会抛出out of memory异常。
1 void MyOutOfMemory() 2 { 3 cout << "Out of memory error!" << endl; 4 abort(); 5 } 6 7 int main() 8 { 9 set_new_handler(MyOutOfMemory); 10 int *verybigmemory = new int[0x1fffffff]; 11 delete verybigmemory; 12 }
这里预先设定好new异常时调用的函数为MyOutOfMemory,然后故意申请一个很大的内存,就会走到MyOutOfMemory中来了。
好,我们更进一步,现在想要在不同的类里面定制不同的new_handler处理机制,一种想法是在类内部定义set_new_handler函数,将new_handler作为私有的成员变量,具体的new_handler函数可以由构造函数传入,但编译器要求set_new_handler是静态的,所以通过构造函数传入new_handler不被编译器支持,只能将set_new_handler与operator new都写成静态的,同时定义一个静态的new_handler变量,像下面这样:
1 class Widget 2 { 3 private: 4 static new_handler CurrentHandler; 5 6 public: 7 void set_new_handler(new_handler h) throw() 8 { 9 CurrentHandler = h; 10 } 11 static void* operator new(size_t size) 12 { 13 Widget::set_new_handler(CurrentHandler); 14 return ::operator new(size); 15 } 16 }; 17 18 new_handler Widget::CurrentHandler = 0;
属于类的静态变量CurrentHandler用于保存当前环境下的new_handler函数,在operator_new中,先设置成当前的new异常处理函数,再去调用std的operator new,执行内存分配操作。但这里就存在问题了,set_new_handler到下一次设置它为止,一直都是生效的,我们只想在处理这个类对象的分配时用自定义的new_handler函数,但是类似于new int,new char这些基本类型,还是希望走默认的new_handler(就是null,就是什么也不执行,如我们期望,这样会抛出异常)。
一种自然的想法,就是在调用operator new末尾处还原new_handler,这就需要保存之前的new_handler,为此,我们构造一个NewHandlerHolder类,像下面这样:
1 class NewHandlerHolder 2 { 3 private: 4 new_handler SavedHandler; 5 NewHandlerHolder(const NewHandlerHolder&); 6 NewHandlerHolder& operator= (const NewHandlerHolder&); 7 8 public: 9 explicit NewHandlerHolder(new_handler h) :SavedHandler(h){} 10 ~NewHandlerHolder() 11 { 12 set_new_handler(SavedHandler); 13 } 14 };
这里有一个SavedHandler成员变量,它在NewHandlerHolder构造时确定具体的指向(其实就是指向系统默认的new_handler函数(即null),将拷贝构造函数与赋值运算符重载设置为private是为了防止出现拷贝的行为(在编译期就可以阻止),这点可以参照之前的条款。还要特别注意这里的析构函数,它调用了set_new_handler,将new异常的处理恢复成SavedHandler(其实就是null)。
这样我们重新整理一下Widget,如下:
1 class Widget 2 { 3 private: 4 static new_handler CurrentHandler; 5 6 7 public: 8 static new_handler set_new_handler(new_handler h) throw() 9 { 10 new_handler OldHandler = CurrentHandler; 11 CurrentHandler = h; 12 return OldHandler; 13 } 14 static void* operator new(size_t size) 15 { 16 NewHandlerHolder h(Widget::set_new_handler(CurrentHandler)); 17 return ::operator new(size); 18 } 19 }; 20 21 new_handler Widget::CurrentHandler = 0;
为了返回系统默认的new_handler,我们在set_new_handler处理完之后,进行了旧handler的返回,同时在operator new的调用中进行了NewHandlerHolder的包装,这样在return之后,h会自动调用析构函数,恢复成默认的new_handler。
到这一步,本条款的重要内容已经说完了,但为了避免重复劳动,即为每一个需要重定义new_handler的类都写一份set_new_handler和operator new,书上在最后对之进行了封装,其实就是将Widget专门作为这两个函数(set_new_handler和operator new)的类,然后将需要自行处理new_handler的类public继承于Widget即可。
最后总结一下:
set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。