Effective C++ Item 49解析

一、基本知识

在理解 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];
	......
}

当然这里自定义的异常处理函数只是很简单的自定义函数,实际情况中的异常处理函数要比这复杂很多,且满足以下条件:

  1. 让更多内存可用 (通过异常处理函数释放一些内存)
  2. 安装另一个new-handler (在异常处理函数内部写入 set_new_handler(anotherhandler))
  3. 卸除new-handler (在异常处理函数内部写入set_new_handler(0))
  4. 抛出bad_alloc(或派生自_alloc的异常)
  5. 不返回 (内部调用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的时候,在函数体内应该包含以下三个步骤:

  1. 将class内存分配异常处理函数装载成全局异常处理函数
  2. 存储系统默认异常处理函数以待后续还原
  3. 调用全局 operator new 完成内存分配
  4. 将系统资源-默认异常处理函数归位

所以,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 的唯一性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值