关于C++异常,你必须知道的


关于C++异常,你必须知道的文章插图
关于C++异常,你必须知道的文章插图
本文是作者翻译过C++之父Bjarne Stroustrup的技术文章C++核心准则中有关C++中异常的文章之后的总结 , 希望读者通过本文可以对C++异常有一个全面 , 快速的了解:
异常处理机制希望解决的问题
为了使用错误处理系统化 , 健壮和不繁琐 。 例如下面的代码:
void f2(int i)// Clumsy and error-prone: explicit release{int* p = new int[12];// ...if (i < 17) {delete[] p;throw Bad{"in f()", i};}// ...}
关于C++异常,你必须知道的文章插图
代码中的作者需要针对每种错误进行处理 , 更为复杂的是当程序的规模达到一定程度之后 , 在各个模块之间和调用的各个层级之间传递错误信息也会变成一个巨大的负担 。 异常就是为了解决这个问题而出现的 。
推荐使用异常的情况
一般情况下会认为异常意味着重大的例外事件和错误 。 例如下面的情况:

  • 一个前提条件没有满足
  • 构造函数无法构造对象(无法建立类的不变式)
  • 越界错误(例如 v[v.size()]=7)
  • 无法获取资源(例如:网络断)
通过抛出异常来向调用者表明函数无法执行指定的任务 。
不应该使用异常的情况
循环的正常终止 , 处理的正常结束都是正常和期待的动作 , 不应该被视为异常 。 这种做法可以保证错误处理和“普通的代码”分离 。 C++编译器会以异常处理很罕见为前提进行代码优化 。 不要使用将抛出异常作为从函数中返回结果的另一种方式使用 。
使用异常时应防止资源泄露
资源泄露通常都是不可接受的 。 如果只是简单的去掉原有的错误处理代码并增加异常抛出和处理代码 , 通常会发生资源泄露 。 例如下面的代码:
void leak(int x)// don't: may leak{auto p = new int{7};if (x < 0) throw Get_me_out_of_here{};// may leak *p// ...delete p;// we may never get here}手动释放资源虽然不是完全做不到 , 但是工作量巨大且容易引发错误 。
void f2(int i)// Clumsy and error-prone: explicit release{int* p = new int[12];// ...if (i < 17) {delete[] p;throw Bad{"in f()", i};}// ...}
关于C++异常,你必须知道的文章插图
这样的代码过于冗长 , 甚至比不用异常的代码更加冗长 。 在更大规模的 , 存在更多的抛出异常的可能性的代码中 , 显式释放资源会更加繁复和易错 。 解决这个问题的方法是RAII(“资源请求即初始化”) , 它是防止泄露最简单 , 更加系统化的方式 。
void f3(int i)// OK: resource management done by a handle (but see below){auto p = make_unique(12);// ...if (i < 17) throw Bad{"in f()", i};// ...}
关于C++异常,你必须知道的文章插图
另外一个解决方案(通常更好)是用局部变量来避免使用指针 。
void no_leak_simplified(int x){vector v(7);// ...}定义和使用自己的异常类型
使用用户定义类型的好处是几乎和其他人的异常发生冲突 。 这种问题在代码规模变大之后会在不知不觉出现 。 继承自exception的标准库类应该只用于基类或只要求“通常”处理的异常 。 和内置类型相似 , 对它们的使用也有可能和其他人的使用发生冲突 。
使用常量引用形式捕捉继承体系中的异常
为了避免数据截断 。 大多数处理程序不会改变异常的内容 , 因此通常我们同时推荐使用常量形式 。
正确排列catch子句
catch子句按照它们表示的次序行 , 一个子句处理之后 , 其他子句不再执行 。
void f(){// ...try {// ...}catch (Base// ...}
关于C++异常,你必须知道的文章插图
但是异常声明让错误处理更脆弱 , 并强制产生运行时成本 , 已经从C++标准中被移除了 。 在不会抛出任何异常时 , 使用noexcept或者和它等价的throw()是才更加正确的做法 。
关于异常代价和性能
很多关于异常的大量恐惧都是被误导的 。 当在没有被指针或复杂的控制结构搞乱的代码环境中使用异常时 , 异常处理几乎总是可以接受的(无论是时间还是空间维度) , 几乎总是可以带来更好的代码 。
在谴责异常或抱怨异常的成本过高之前 , 考虑使用错误代码时的成本和复杂度 。 如果你担心性能 , 进行测量(而不是无根据的怀疑 , 译者注)
参考资料