异常规范: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。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐