如果您在“我们不使用例外”阵营,那么您如何使用标准库?

注意:我不是在这里扮演魔鬼的拥护者或者类似的东西 – 我真的很好奇,因为我本人不在这个阵营。

标准库中的大多数types都具有可以抛出exception(例如,如果内存分配失败)的突变函数或可以抛出exception的非突变函数(例如超出索引访问器的范围)。 除此之外,许多免费函数可能会抛出exception(例如operator newdynamic_cast<T&> )。

在“我们不使用例外”的情况下,你如何处理这个问题?

  • 你想永远不要调用一个可以抛出的函数吗? (我看不出如何扩展,所以我很感兴趣,听听你是如何做到这一点的)

  • 你可以使用标准库抛出,你把“我们不使用exception”当作“我们从不抛出exception,我们从来没有从其他代码中捕获exception”?

  • 你是否通过编译器开关完全禁用exception处理? 如果是这样,标准库的exception抛出部分如何工作?

  • 编辑你的构造函数,他们可以失败,或者你是否按惯例使用一个专用的初始化函数,可以返回一个失败的错误代码(构造函数不能),或者你做别的什么?

编辑在问题问世1周后进行小幅度的澄清……下面的评论和问题的大部分内容都集中在例外与“其他”的方面。 我的兴趣不在于此,但是你select“别的东西”的时候,你如何处理那些抛出exception的标准库部分呢?

我会回答自己和我的世界的angular落。 我写的C ++ 14(编译器有更好的支持将是17)延迟关键的金融应用程序,处理大量的金钱,永远不能下降。 规则集是:

  • 没有例外
  • 没有rtti
  • 没有运行调度
  • (几乎)没有inheritance

内存是合并和预分配的,所以初始化后没有malloc调用。 数据结构要么是不死的,要么是普通的,所以析构函数几乎不存在(有一些例外,比如范围守卫)。 基本上,我们正在做C +types安全+模板+ lambdas。 当然,通过编译器开关来禁用exception。 至于STL,它的好的部分(即algorithm,数字,type_traits,迭代器,primefaces,…)都是可用的。 exception抛出部分与运行时内存分配部分和半OO部分很好地吻合,所以我们得到摆脱所有的一蹴而就:stream,容器,除了std :: array,std :: string。

为什么这样做?

  1. 因为像OO一样,例外通过在其他地方隐藏或移动问题来提供虚幻的清洁,并使程序的其余部分更难以诊断。 当你没有使用“-fno-exceptions”进行编译时,你所有干净而且很好的函数都必须忍受怀疑是失败的。 在代码库周围进行广泛的合理性检查要比使efery操作失败容易得多。
  2. 因为exception基本上是远程GOTO有一个未指定的目的地。 你不会使用longjmp(),但是exception可以说更糟。
  3. 因为错误代码是优越的。 您可以使用[[nodiscard]]强制调用代码进行检查。
  4. 因为exception层次结构是不必要的。 大多数情况下,区分什么是错误的,什么时候错误是没有意义的,这很可能是因为不同的错误需要不同的清理,明确的信号会更好。
  5. 因为我们有复杂的不variables来维护。 这意味着,无论在内心深处都有代码,需要有跨国的保证。 有两种方法可以做到这一点:或者让命令程序尽可能纯粹(即:确保永不失败),或者拥有不可变的数据结构(即:使失败恢复成为可能)。 如果你有不可变的数据结构,当然你可以有例外,但是你不会使用它们,因为当你使用和types的时候。 函数式数据结构虽然速度很慢,但另一种方法是使用纯函数,并以C语言之外的无例外语言(除C ++或Rust之外)进行。 不pipeD看起来多漂亮,只要它没有清理GC和exception,这是一个不可选项。
  6. 你有没有testing过你的例外,就像你会明确的代码path? 那么“永远不会发生”的例外呢? 当然,你不这样做,而当你真的遇到了这些例外,你被拧了。
  7. 我在C ++中看到了一些“漂亮”的exception中立代码。 也就是说,无论它调用的代码是否使用exception,它都不会发生边缘情况,从而达到最佳效果。 他们真的很难写,我怀疑,如果你想保持所有exception保证,很难修改。 但是,我还没有看到任何抛出或捕获exception的“漂亮”代码。 我所见过的所有与exception直接交互的代码都是非常难看的。 写入exception中立代码所花费的精力完全相当于从抛出或捕获exception的蹩脚代码中保存的工作量。 “美丽”在引号中是因为它不是真正的美:它通常是僵化的,因为编辑它需要额外的保持exception中立的负担。 如果您没有unit testing故意全面地滥用例外来触发这些边缘情况,那么即使是“美丽的”exception中立的代码也会腐化成粪便。

在我们的例子中,我们通过编译器禁用exception(例如gcc的-fno-exceptions )。

在gcc的情况下,他们使用一个名为_GLIBCXX_THROW_OR_ABORT的macros定义为

 #ifndef _GLIBCXX_THROW_OR_ABORT # if __cpp_exceptions # define _GLIBCXX_THROW_OR_ABORT(_EXC) (throw (_EXC)) # else # define _GLIBCXX_THROW_OR_ABORT(_EXC) (__builtin_abort()) # endif #endif 

(你可以在最新gcc版本的libstdc++-v3/include/bits/c++configfind它)。

那么你有理由不得不处理抛出exception的事实。 您仍然可以捕获信号并打印堆栈(对于这个问题,有一个很好的答案可以解释这一点),但是最好避免这种事情发生(至less在发布时)。

如果你想要一些例子,而不是像

 try { Foo foo = mymap.at("foo"); // ... } catch (std::exception& e) {} 

你可以做

 auto it = mymap.find("foo"); if (it != mymap.end()) { Foo foo = it->second; // ... } 

我还想指出的是,当询问不使用exception时,关于标准库有一个更一般的问题:当你在“我们不使用exception”的阵营中使用标准库吗?

标准库很重。 在一些“我们不使用exception”的阵营中,比如许多GameDev公司,使用更适合STL的替代品 – 主要是基于EASTL或者TTL。 这些库无论如何都不使用exception,这是因为第八代控制台没有处理得太好(甚至根本就没有)。 对于一个尖端的AAA生产代码来说,例外情况太重了,所以这是一个双赢的情况。

换句话说,对于很多程序员来说,closuresexception并不是完全不使用STL。

注意我使用exception…但是我被迫不去。

你想永远不要调用一个可以抛出的函数吗? (我看不出如何扩展,所以我很感兴趣,听听你是如何做到这一点的)

这可能是不可行的,至less是大规模的。 许多function可以抛出,避免它们完全瘫痪你的代码库。

你可以使用标准库抛出,你把“我们不使用exception”当作“我们从不抛出exception,我们从来没有从其他代码中捕获exception”?

你几乎必须要这样做…如果库代码将抛出一个exception,而你的代码不会处理它,终止是默认的行为。

你是否通过编译器开关完全禁用exception处理? 如果是这样,标准库的exception抛出部分如何工作?

这是可能的(早些时候某些项目types已经stream行了)。 编译器可以/可以支持这一点,但是您需要咨询他们的文档以了解结果将会是什么样的(以及在这些条件下支持哪些语言function)。

一般来说,当抛出exception时,程序将需要中止或以其他方式退出。 一些编码标准仍然需要这个,JSF编码标准(IIRC)。

那些“不使用例外”的一般策略

大多数函数都有一组前置条件,可以在调用之前进行检查 。 检查这些。 如果不满足,就不要打电话; 回退到代码中的error handling。 对于那些你不能检查以确保前提条件得到满足的函数…不多,程序可能会中止。

你可以看看避免引发exception的库 – 你在标准库的背景下提出这个问题,所以这不太适合账单,但它仍然是一个选项。

其他可能的策略; 我知道这听起来很陈腐,但select一种不使用它们的语言。 C可以做得很好…

我的问题(你与标准库的交互,如果有的话)的关键,我很有兴趣听到你的构造函数。 他们可以失败吗,或者你是否按惯例使用了一个专门的初始化函数的2步构造,可以在失败时返回一个错误代码(构造函数不能)? 或者你有什么策略?

如果使用构造函数,通常有两种方法来指示失败;

  1. 设置内部错误代码或enum来指示失败以及失败是什么。 这可以在物体的构build和采取适当的行动之后被询问。
  2. 不要使用构造函数(或者至less只构造构造函数中不能失败的东西 – 如果有的话),然后使用某种types的init()方法来执行(或完成)构造。 成员方法然后可以返回一个错误,如果有一些失败。

init()技术的使用通常是有利的,因为它可以被链接并且比内部的“错误”代码更好地扩展。

再次,这些技术来自exception情况不存在的环境(如C)。 使用诸如C ++之类的语言无例外地限制了它的可用性和标准库广度的有用性。

不要试图完全回答你所问的问题,我只是将谷歌作为代码库的一个例子,它不利用exception作为处理错误的机制。

在Google C ++代码库中,每个可能失败的函数都会返回一个status对象,这个status对象具有ok方法来指定被调用者的结果。
如果开发者忽略返回status对象,他们已经configuration了GCC来使编译失败。

另外,从他们提供的小开源代码(比如LevelDB库)看来,他们似乎并没有使用STL,所以exception处理变得很less见。 正如泰特斯·温特斯(Titus Winters)在CPPCon的讲座中所说:“尊重标准,但不要崇拜标准”。