More Effective C++ 条款14 明智运用exception specifications
1. Exception specifications作为函数声明的一部分,用于指出(并不能限制)函数可能会抛出的异常函数.C++规定,一个拥有exception specification的函数指针只能被赋予一个有着相同或更为局限的exception specification的函数地址,因而编译器要保证"在函数指针传递之际检验exception specifications".(但visual studio 2013不支持此项要求)
2. 当函数抛出exception specification未列出的异常时,程序会调用标准库terminate函数,terminate函数又调用abort函数终止程序而不会留给程序员挽救的机会.这种情况很容易发生:当函数调用一个exception specification限制程度低于自身的函数时(C++明定编译器必须允许"调用某个函数而该函数可能违反调用函数本身的exception specification").
通常需要采用以下策略防止"非预期expection"的出现:
1). 对于带类型参数的函数模板,要尽量避免使用exception specifications,因为不同类型对于相同行为的定义不同,抛出的异常也就不同,因而函数模板很难或不可能确定它具现化的函数实体所可能抛出的异常.
2). "如果A函数内调用了B函数,而B函数无exception specifications,那么A函数本身也不要设定exception specifications".这个比较容易实现,但有一种情况容易被忽略:使用回调(callback)函数时.
对于以下类定义:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
typedef void(*CallBackPtr)(int, int, void*); class CallBack{ public: CallBack(CallBackPtr(fPtr), void*dataToPassBack) :func(fPtr), data(dataToPassBack){} void makeCallBack(int eventXLocation, int eventYLocation)const throw(){ func(eventXLocation, eventYLocation,data); } private: CallBackPtr func; void *data; };
对于以下代码,仍会出现"函数调用一个exception specification限制程度低于自身的函数"的问题:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void callBackFun1(int eventXLocation, int eventYLocation, void*dataToPassBack){} void* callBackData=NULL; CallBack(callBackFun1, callBackData); //callBackFcn1可能会抛出exception而func声明不抛出任何异常,但语法上无错误
解决方法就是将
typedef void(*CallBackPtr)(int, int, void*);
改为:
typedef void(*CallBackPtr)(int, int, void*)throw();
正如1所提,对于以下代码编译器会阻止并报错(但正如1所提,visual studio不会):
CallBack(callBackFun1, callBackData); //错误!不能将callBackFun1赋给func
3). 处理"系统可能抛出的异常".例如内存分配失败时抛出的bad_alloc异常(由operator new和operator new[]抛出).
除了以上预防未预期exception的方法之外,还有一种方法可以处理未预期exception:自行指定当未预期的exception抛出时所调用的函数(而不是采用默认的terminate函数),通过调用set_unexpected函数加以设定(详见http://www.cplusplus.com/reference/exception/set_unexpected/):
typedef void(*unexpected_handler)();
unexpected_handler set_unexpected (unexpected_handler f) noexcept;
(noexcept是C++11新引入的修饰符,表示其修饰的函数不会抛出异常,与throw()动态异常声明不同的是,在C++11中如果noexcept修饰的函数抛出了异常,编译器可以选择直接调用std::terminate()函数来终止程序的运行.详见http://book.2cto.com/201306/25351.html)
如果用set_unexpected函数指定了unexpected_handler,当函数抛出未预期的exception时,程序就会调用定制的unexpected_handler,定制的unexpected_handler或者终止程序(比如调用abort),或者抛出一个异常,如果抛出异常,那么该异常必须在调用unexpected_handler的函数的expection specification中,否则程序仍会调用terminate函数.
(遗憾的是,Visual C++ 接受但并不实现此规范,详见http://bbs.csdn.net/topics/90000629,http://blog.csdn.net/x356982611/article/details/22791151)
自定义的unexpected_handler有一种实现方式如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void convertUnexpected(){ throw();//抛出的是bad_exception }
这种实现基于:"如果非预期函数的替代者重新抛出当前的exception,该exception会被标准类型bad_exception取而代之".因此,如果用以上方式实现unexpected_handler,那么只要每一个exception specification都含有bad_exception,就再也不用担心非预期的exception导致程序终止执行.
3. Exception specification对于"函数希望抛出什么样的exception"提供了说明,并且对于抛出exception specification中未指定行为的情况提供了默认行为.但它也有其缺点:编译器只对它们做局部性检验导致很容易违反,可能会更上层的exception函数处理未预期的exception等.对于exception specification的使用需要明智使用.