一、基本知识
在理解 item 49:了解new-handler的行为 之前必须要了解如下C++的基本知识
- 函数指针
函数指针的定义式:返回值类型 (*func)([形参列表])
int func(int x); //申明一个函数
int (*f) (int x); //申明一个函数指针
f=func; //将func函数的首地址赋给函数指针f;
f=0; //函数指针初始化 - ::operator new是C++的全局函数,可重载
C++中与内存分配new有关的函数/运算符:new /::operator new / new placement
其中仅仅只有::operator new 允许重载,通常用于自定义内存分配函数
二、new-handler及其指向的异常处理函数
item 49中讲到operator new在抛出异常之前会调用客户指定的异常处理函数,为了指定这个异常处理函数,必须使用set_new_handler()装载异常处理函数:
namespace std{
typedef void (*new_handler) ();
new_handler set_new_handler(new_handler p) throw( );
}
set_new_handler()的形参是一个函数指针,指向一个异常处理函数,调用set_new_handler()之后,将其装载成全局异常处理函数。其返回值也是一个函数指针,指向被替代的全局异常处理函数(执行set_new_handler()之前的全局异常处理函数)
在具体使用中,可以这样操作:
定义异常处理函数 OutofMem( ),然后在使用 operator new函数之前装载它
void OutofMem()
{
std::cout<<"the Memory can't satisfy the request!"<<endl;
std::abort();
}
int main(){
std::set_new_handler(OutofMem);
int* BigArrays=new int[10000000000];
......
}
当然这里自定义的异常处理函数只是很简单的自定义函数,实际情况中的异常处理函数要比这复杂很多,且满足以下条件:
- 让更多内存可用 (通过异常处理函数释放一些内存)
- 安装另一个new-handler (在异常处理函数内部写入 set_new_handler(anotherhandler))
- 卸除new-handler (在异常处理函数内部写入set_new_handler(0))
- 抛出bad_alloc(或派生自_alloc的异常)
- 不返回 (内部调用abort()或者exit())
三、以分配对象类型来指定异常处理函数
因为在前面提到全局::operator new函数是可以重载的,所以针对不同class,我们可以将global new operator重载成当前class的 static 成员函数 并以成员函数的方式申明自己new-handler和set_new_handler();
class Widget{
public:
static std::new_handler set_new_handler(std::new_handler p) throw();
static void* operator new(std::size_t size) throw(std::bad_alloc);
private:
static std::new_handler currentHandler;
}
std::new_handler Widget::currentHandler=0; //static变量只能在class外被定义,在这里初始化为0
std::new_handler Widget::set_new_handler(std::new_handler p) throw();
{
std::new_handler oldeHandler=currentHandler;
currentHandler=p;
return oldHandler;
}
Widget的set_new_handler()必须要完成对指向异常处理函数的函数指针p的装载,并且还要返回系统默认的异常处理函数,以便后面退出new操作时,资源处理类可以还原系统默认异常处理函数。
我们把默认全局异常处理函数视为系统资源,所以当前new操作执行完毕之后,应该将资源归位;才不至于对下次执行new操作造成影响。系统默认异常处理函数的存储、还原由资源处理类NewHandlerHolder完成:
class NewHandlerHolder{
public:
NewHandlerHolder;handler(nh){ } //构造函数 构造对象的时候,传入系统默认异常处理函数
~NewHandlerHolder()
{
std::set_new_handler(handler); //析构函数 析构的时候,将默认异常处理函数装载归位
}
private:
std::new_handler handler;
}
我们在Widget class 中重载operator new的时候,在函数体内应该包含以下三个步骤:
- 将class内存分配异常处理函数装载成全局异常处理函数
- 存储系统默认异常处理函数以待后续还原
- 调用全局 operator new 完成内存分配
- 将系统资源-默认异常处理函数归位
所以,Widget class 的operator new 设计如下:
void* Widget::operator new(std::size_t size) throw(std::bad_alloc)
{
NewhandlerHolder h(std::set_new_handler(currentHandler));
return ::operator new(size);
}
上述Widget operator new中,构造了一个NewHandlerHolder类型的局部变量,因此会调用NewHandler clas的构造函数完成对cuerentHandler的装载,然后return全局operator new ,最后内存分配执行完毕后调用NewHandlerHolder class的析构函数将系统默认异常处理函数归位。
当然,每一个class的set_new_handler、重载operator new函数都是一样的,可以使用泛型编程的思想,申明一个模板类,然后通过实例化模板类并继承的方式,完成相应class的内存分配功能,这样就避免了在不同class中的重复申明这两个函数:
template<typename T>
class NewHandlerSupport{
public:
static std::new_handler set_new_handler(std::new_handler p) throw();
static void* operator new(std::size_t size) throw(std::bad_alloc);
private:
static std::new_handler currentHandler;
};
template<typename T>
std::new_handler
NewHnadlerSupport<T>::set_new_handler(std::new_handler p) throw();
{
std::new_handler oldeHandler=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 ::operator new(size);
}
template<typename T>
std::new_handler NewhandlerSupport::currentHandler=0;
其实,NewHandlerSupport class中的模板参数T并没有被用到,但是通过传入不同的T,可以实例化出不同的类,达到每一个类都有自己的currentHandler的目的;所以模板参数T并不是多余的。通过以下例子可以得到多份各个继承类专属的currentHandler
class A public:NewHandlerSupport<A>{
......
};
class B:public:NewHandlerSupport<B>{
......
};
其中,模板参数A,B保证了class A和class B中currentHandler 的唯一性。