C++异常规范:高效运用的实用技巧
异常规范:C++98引入异常规范机制,旨在通过限制函数可抛出的异常类型提升代码可读性。该特性在C++11中被更灵活的noexcept关键字取代。
异常规范存在显著缺陷:当函数违反规范抛出未声明的异常时,会触发unexpected处理流程。默认情况下,该流程将调用terminate导致程序立即终止。这种严格的行为机制要求开发者必须谨慎使用异常规范,尤其在调用未声明异常规范的第三方库函数时更需特别注意。
由于异常规范是局部检验,当有异常规范的函数调用其他函数时,编译器不会因为其他函数会抛出异常规范不允许的异常而报错:
//函数B会抛出异常
void B();
//函数A的异常规范是不抛出任何异常
void A()throw(){
B(); //A调用B可能导致违反异常规范
}
此时,使用A函数随时可能导致程序中止。为了避免这个问题,下面提出了三条规则:
一、避免将模板与异常规范结合使用
template<class T>
void copy(T& t1,const T& t2) throw(){
t1 = t2;
}
我们设计了一个模板函数,该函数通过调用对象的赋值运算符实现复制操作,并声明为不抛出任何异常。虽然表面上看逻辑正确,但如果传入对象的赋值运算符被重写为可能抛出异常的实现,该模板函数将导致程序意外终止。
这种情况表明,模板与异常规范不应混用,因为无法保证模板参数类型的具体实现是否会引发异常。
二、避免回调函数导致的异常
回调函数是我们将函数指针类型化,可以自定义此函数指针指向的函数,并通过函数指针调用。
//定义函数指针类型
typedef void (*FunPtr)(int a);
//创建一个函数指针
FunPtr funPtr;
//其他函数通过函数指针使用函数
void Fun() throw(){
funPtr(a);
}
funPtr可能是同类型的任何函数,我们并不能确定其是不是一个可能抛出异常的函数。因此,函数Fun随时可能导致程序中止。
为了避免这个情况,有异常规范的函数尽量不要使用回调函数。如果必须要使用,可以给函数指针类型加上异常规范(可能有的编译器不支持):
//定义有异常规范的函数指针类型
typedef void (*FunPtr)(int a) throw();
//当funPtr指向没有throw()异常规范的指针时会报错
FunPtr funPtr;
三、小心库和系统的异常
调用库函数或C++内置操作时可能触发的异常,若逐句添加try-catch块将导致代码冗余。
更优的处理方式是:
- 对关键代码段进行集中异常捕获
- 区分可恢复异常与致命错误
- 结合RAII机制管理资源
这种策略既能保证异常安全性,又可避免过度防御型编程带来的可读性下降。
因此,针对这种错误,我们直接改造违反异常规范时调用的unexpected函数:
//定义一个异常
class Unex{};
//重写unexpected函数
void newUnexpected(){
//抛出统一的异常
throw Unex();
}
//将unexpected设置为newUnexpected
unexpected(newUnexpected);
此时,库和C++内置的函数与操作符抛出的任何异常都会调用newUnexpected,变为一个统一的Unex异常。我们只用在异常规范中添加Unex即可。
如果不想创建Unex异常,可以这样做:
//重写unexpected函数
void newUnexpected(){
//抛出统一的异常
throw;
}
//将unexpected设置为newUnexpected
unexpected(newUnexpected);
此时,newUnexpected会将任何异常转为bad_exception对象抛出,相当于使用bad_exception代替了Unex。
更多推荐



所有评论(0)