博客 阅读时间 5 分钟

用还是不用:关于 C++ 异常的大辩论

分享本文
To Use or Not To Use: C++ Exceptions Debate

20 多年前,C++ 加入了异常处理机制。经过多年开发者对这一特性的实践,如今我们已经积累了关于其优缺点的宝贵反馈。

让我们来看看 C++ 社区一些知名人物的观点,看看他们对使用异常的看法。

一、C++ 专家

Herb Sutter,摘自他的文章:

总结:优先使用异常而非错误码来报告错误。仅在无法使用异常的情况下(当你无法控制所有可能的调用代码、无法保证它们会用 C++ 编写并使用相同的编译器和兼容的编译选项以使异常处理正常工作时),以及对不属于错误的条件,才使用状态码(如返回码或 errno)。当不需要恢复或无法恢复时,使用其他方法,如优雅或不优雅地终止。

Bjarne Stroustrup,摘自他的 FAQ 页面:

使用异常对我有什么好处?基本的答案是:使用异常进行错误处理能让你的代码更简单、更整洁,也更不容易遗漏错误。但"经典的 errno 加 if 语句"有什么问题呢?基本的答案是:使用那种方式,你的错误处理代码和正常代码会紧密交织在一起。这样一来,代码会变得混乱,也很难确保你已经处理了所有错误(想想"意大利面条式代码"或"一团乱麻的测试")。

Scott Meyers,摘自他的《Effective C++》一书:

四十年前,满是 goto 的代码被认为是完全良好的实践。如今我们努力编写结构化的控制流。二十年前,全局可访问的数据被认为是完全良好的实践。如今我们努力封装数据。十年前,编写函数时不考虑异常的影响被认为是良好实践。如今我们努力编写异常安全的代码。

Joel Spolsky,摘自他的文章:

有人问我为什么不喜欢用异常编程。无论 Java 还是 C++,我的原则是:

1 – 绝不出于自己的意愿抛出异常;2 – 对于我使用的库可能抛出的任何异常,总是在抛出的同一行捕获它并立即处理。

Andrei Alexandrescu,摘自他的文章

《编写异常安全的代码很难》

在同一篇文章的后半部分,他解释了如何使用作用域守卫(scope guard)惯用法来简化异常的使用:

ScopeGuard 是"初始化即资源获取"这一 C++ 惯用法典型实现的泛化。不同之处在于,ScopeGuard 只关注清理部分——你负责获取资源,ScopeGuard 负责释放资源。(事实上,清理可以说是这个惯用法中最重要的部分。)

Jon Kalb,摘自他的网站

安全地使用异常是一个不平凡的问题,业界为此已经努力了近二十年。如果你对异常安全心存恐惧、不确定或怀疑,或者只是想了解在 C++ 中使用异常的最佳实践,那么这场演讲正适合你。我们将从"我们试图解决的问题是什么?"开始,讨论各种替代方案,正视使用异常带来的挑战,并介绍一些出于好意但方向错误的安全尝试。然后,我将提出一套作为异常安全使用基础的指导原则和扎实的实现技术,包括如何从异常不安全的遗留代码库迁移过来。

二、真实项目

摘自 Boost 风格指南:

什么时候应该使用异常?

简单的答案是:"只要异常的语义和性能特征是合适的。"

摘自谷歌风格指南

表面上看,使用异常的好处大于其代价,尤其是在新项目中。然而,对于既有代码,引入异常会对所有依赖它的代码产生影响。如果异常可能传播到新项目之外,那么把这个新项目集成到既有的无异常代码中也会变得很麻烦。由于谷歌现有的大多数 C++ 代码都没有准备好处理异常,因此要采用会生成异常的新代码就相对困难。

Clang

Clang 团队决定不使用 C++ 异常;下面摘自他们的编码标准:

为了减小代码和可执行文件的体积,LLVM 不使用 RTTI(例如 dynamic_cast<>)或异常。这两个语言特性违背了 C++"你只为你使用的东西付费"的基本原则:即使代码库中从未使用异常,或者某个类从未使用 RTTI,它们也会导致可执行文件膨胀。因此,我们在整个代码中全局关闭了它们。

另一方面,LibreOffice 使用了 C++ 异常。

纵观许多知名的开源 C++ 项目,并没有明显的偏好:两种做法都被广泛使用。

三、C++ 社区

如果看看论坛、文章和社交媒体上的讨论,我们可以在 C++ 社区中识别出三种主要观点:

  • 使用它们——这是处理异常情况的最佳方式。
  • 使用它们,但要非常小心——它们有一些副作用。
  • 绝不使用它们。

作为一名 C++ 开发者,我感到非常困惑。我到底该不该使用它们?

所有这些 C++ 专家都指出了一个重要问题:编写异常安全的代码并非易事。我们可以把 C++ 开发者分为四类:

第一类:使用 C++ 异常,但不关心异常安全。

第二类:使用 C++ 异常,并且了解异常安全问题。

第三类:不使用异常,但也没有完全理解异常的利弊。

第四类:不使用异常,因为他们完全理解其缺点。

第二类和第四类的开发者清楚地知道自己在做什么。然而,第一类和第三类的开发者并没有完全理解异常机制,这可能会影响他们代码的质量。也许争论是否使用异常本身就是个错误的问题。更重要的是你是否理解使用它们的利弊。根据你的具体约束做出选择,这取决于你自己。如果你选择了其中一种方式,没有人能责怪你。

分享本文