首页 > 代码库 > 定制new 和 delete
定制new 和 delete
1、了解new-handler的行为
当operator new 抛出异常以反映一个未满足的内存需求之前,他会先调用一个客户指定的错误处理函数,一个所谓的new-handler。为了指定这个“用以处理内存不足”的函数,客户必须调用set_new_handler,那个声明于<new>的一个标准程序库函数:
namespace std{ typedef void (*new_handler)(); new_handler set_new_handler(new_handler p) throw(); }
注:“throw()”是一份异常明细,表示该函数不抛出任何异常。
你可以这样使用set_new_handler:
//以下是当operator new 无法满足分配足够内存时,应该被调用的函数void outOfMem(){ std::cerr << "Unable to satisfy request for memory!\n"; std::abort(); }
当operator new 无法满足内存申请要求时,他会不断调用new_handler函数,直到找到足够内存。设计一个良好的new_handler函数必须做以下事情:
(1) 让更多的内存可被使用
这边造成operator new 内下一次内存分配动作可以成功。实现这一策略的一个做法是,程序一开始执行就分配一大块内存,而后当new_handler第一次被调用,就将它们释还给程序使用。
(2) 安装另一个new_handler
如果目前这个new_handler无法取得更多内存,或许它知道那个new_handler由此能力
(3) 卸除new_handler
也就是将null指针传给set_new_hanlder。一旦没有安装任何new_handler,operator new会在内存分配不成功时抛出异常。
(4) 抛出bad_alloc(或派生自bad_alloc)的异常
这样的异常不会被operator new捕捉,因此会被传播到内存所求处。
(5) 不反回
通常调用abort或exit直接退出程序。
当你希望以不同方式处理内存分配情况失败情况的时候,你希望是被分配物属于哪个class而定。C++并不支持class专属之new-handlers,你可以自己实现出这种行为。
class Widget{public: static std::new_handler set_new_handler(std::new_handler)throw(); static void* operator new(std::size_t size)throw(std::bad_alloc);private: static std::new_handler currentHandler; }// Static 成员必须在class定义式外被定义(除非它们是const 而且是整数型)std::set_handler Widget::currentHandler=0;//在class实现文件内初始化为null;//Widget 内的set_new_handler 函数会将它获得的指针存储起来,然后返回先前存储的指针。std::new_handler Widget::set_new_handler(std::new_handler p)trow(){ std::new_handler oldHandler = currentHandler; currentHandler=p; return oldHandler;}
使用资源处理类来管理资源
class NewHandlerHolder{public: explicit NewHandlerHolder(std::new_handler nh):handler(nh){}//获得当前的new_handler ~NewHandlerHolder(){std::set_new_handler(handler);}//释放它private: std::new_handler handler; NewHandlerHolder(const NewHandlerHolder&); //阻止编译器自动生成拷贝构造函数 NewHandlerHolder& operator=(const NewHandlerHolder&); 阻止编译器自动生成赋值操作};
Widget operator new的实现:
void* Widget::operator new(std::size_t size)throw(std::bad_alloc){ NewHandlerHolder h(std::set_new_handler(currentHandler)); return std::operator new(size);}
Widget的客户应该类似这样使用其new_handling:
void outOfMem();//函数声明,此函数在Widget对象分配内存失败时被调用
Widget::set_new_handler(outOfMem);//安装new_handler函数
Widget* pw1=new Widget;//如果内存分配失败,调用outOfMem
std::string *ps=new std::string;//如果内存分配失败调用global new_handler函数(如果有的话)Widget::set_new_handler(0);//设定Widget专属的new_handler函数为null
Widget* pw2=new Widget;//如果内存分配失败,立刻抛出异常
可以将上面的代码设计成“mixin”风格的base class用以支持class专属的set_new_handler
template<typename T>class NewHandlerSupport{public: static std::new_handler set_new_handler(std::new_handler)throw(); static void* operator new(std::size_t size)throw(std::bad_alloc);private: static std::new_handler currentHandler; };
template<typename T>static std::new_handler NewHandlerSupport<T>::set_new_handler(std::new_handler)throw(){
std::new_handler oldHandler = currentHandler; currentHandler=p; return oldHandler;
}
template<typename T>void* NewHandlerSupport<T>::operator new(std::size_t size)
throw(std::bad_alloc){
NewHandlerHolder h(std::set_new_handler(currentHandler));
return std::operator new(size);
}
template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;
class Widget:public NewHandlerSupport<Widget>{
....//和原来一样,但是不用声明set_new_handler 和 operator new
}
旧的标准要求operator new 在无法分配足够多的内存时,返回null。新一代的operator new 则应该抛出bad_alloc异常。
class Widget{.....}Widget *pw1=new Widget;//如果分配失败,抛出bad_allocif(pw1==0) //这个判断一定会失败Widget *pw2=new (std::nothrow) Widget;//如果分配失败,返回0if(pw2==0) //这个测试可以成功
Nothrow() new 对异常的墙纸保证性并不高,它可以保证operator new不抛出异常,但是如果Widget构造函数中有可能又执行了一次new操作,这样还是会抛出异常。
请记住:
(1) set_new_handler 允许客户指定一个函数,在内存分配失败时被调用
(2) Notrow new 是一个颇为局限的工具,以为它适用于内存分配;后继的构造函数调用还是可能抛出异常。
2、operator new伪代码分析
void* operator new (std::size_t size) throw(std::bad_alloc){ using namespace std; if(size == 0){ //如果size为0,将它视为1bytes申请 size=1; } while(true) { 尝试分配size bytes if(分配成功) return (一个指向分配内存的指针); //分配失败,找出目前的new_handler函数 new_handler globalHandler = set_new_handler(0); set_new_handler(globalHandler); if(globalHandler) (*globalHandler)(); else throw std::bad_alloc(); }}
当申请内存大小为0byte,将申请量视为1byte,有助于简化语言其他部分。
在上述代码中,将new_handling函数指针设为null而后有立刻恢复原样,那是因为没有其他办法可以直接获取new_handling函数指针,所以必须调用set_new_handler找出它来。
3、class专属版的new/delete可以处理“比正确大小更大的申请”
class Base{public: static void* operator new(std::size_t size)throw(std::bad_alloc); static void operator delete(void* rawMemory,std::size_t size) throw(); ......};class Derived : public Base{ //假设Derived没有申明operator newpublic: ........};Derived *p=new Derived;//这里调用base::operator newvoid* Base::operator new(std::size_t size) throw(std::bad_alloc){ if(size!=sizeof(Base)) return std::operator new(size); //如果大小错误,调用标准的operator new 处理 ........}void Base::operator delete(void* rawMemory,std::size_t size) throw(){ if(rawMemory ==0) return ; //检查null指针 if(size!=sizeof(Base)){ std::operator new(size); //如果大小出错,另标准版的operator delete处理此申请 return ; }}
定制new 和 delete