"我们不使用C++例外" - 有什么替代方案?让它崩溃?

Mr.*_*. X 34 c++ exception

"我们不使用C++异常."

如果你不使用异常,那么当出现错误时会发生什么?你刚才让程序崩溃了吗?

Rob*_*ert 26

或者你可以进一步阅读:

从表面上看,使用例外的好处超过了成本,特别是在新项目中.但是,对于现有代码,异常的引入会影响所有依赖代码.如果异常可以传播到新项目之外,那么将新项目集成到现有的无异常代码中也会出现问题.由于Google的大多数现有C++代码都没有准备好处理异常,因此采用生成异常的新代码相对比较困难.

鉴于Google的现有代码不能容忍异常,因此使用异常的成本比新项目中的成本要高一些.转换过程缓慢且容易出错.我们不认为可用的异常替代方案(例如错误代码和断言)会带来很大的负担.

我们反对使用例外的建议不是基于哲学或道德理由,而是基于实际理由.因为我们想在Google上使用我们的开源项目,如果这些项目使用例外情况很难这样做,我们也需要针对Google开源项目中的例外情况提出建议.如果我们不得不从头再做一遍,事情可能会有所不同.

对于Windows代码,此规则有一个例外(没有双关语).

  • 另一个不在谷歌工作的原因. (18认同)
  • @Karel:因为我们中的一些人没有将C++添加到庞大的现有C代码库中,因此应该使用异常来报告错误.谷歌风格指南在风格和格式等方面都很好,但是"我们只使用C++功能的一个子集"部分对于新代码来说根本不是好主意,除非该代码是针对一个意味着不被消费的库. - 新代码. (11认同)
  • 为什么?我已经阅读了整个c ++风格指南,我完全同意大部分的评论 (5认同)

Mic*_*yan 25

不,替代方案是做人们在C中做了多年的事情...你返回一个错误状态代码,指示函数是否成功,并且根据它可能失败的方式,你可能有一个或多个输出参数,在其中指明失败的方式(或者在错误状态代码中包含失败类型,这也是一个个案的事情).


lis*_*isa 11

除了返回码之外的其他例外的替代方案:

  • LISP风格的条件处理程序.
  • 软件信号和插槽,QT风格.
  • 没有堆栈展开的硬件中断或信号处理程序.这是堆栈展开,这是嵌入式设备上的异常问题.
  • longjmp/setjmp
  • 回调函数
  • 标志值,例如errorno(Unix),GetLastError(Windows),glGetError(OpenGL)等.
  • 消息队列,如果程序是事件驱动的
  • 可变的函子.Handler对象作为输入输出参数,指针在出错时更改.
  • 具有错误解决协同例程的光纤或线程
  • asm __int 3或CPU等效
  • 叠加指令或蹦床功能.

...还有很多.上面列出的许多都是嵌入式设备友好的.


Tho*_*ini 7

如果您没有按定义使用异常,则代码不会抛出异常,因此不需要捕获它.

它是"我们不使用C++异常",而不是"我们不会捕获C++异常".

  • @josefx:在标准 C++ 中,写入无效内存位置是未定义的行为。C++ 标准并没有规定它必须抛出异常,事实上在 Linux 上它就没有(它发送一个信号)。实际上,我认为发生这种情况时任何操作系统都不会引发异常。我相信 Windows 可以选择抛出一个结构异常(这与 C++ 异常不同),但仅此而已。 (2认同)
  • @josefx:错误后继续不会导致程序可靠崩溃; 它可能很容易破坏更多数据并导致更多的灾难性故障.您不能依赖未定义行为的行为,并且如果程序处于无效状态,则唯一有效的选项是修复它或终止. (2认同)

Igo*_*aka 6

链接的样式指南很好地解释了它:

从表面上看,使用例外的好处超过了成本,特别是在新项目中.但是,对于现有代码,异常的引入会影响所有依赖代码.如果异常可以传播到新项目之外,那么将新项目集成到现有的无异常代码中也会出现问题.由于Google的大多数现有C++代码都没有准备好处理异常,因此采用生成异常的新代码相对比较困难.

在C++中相对容易创建健壮的代码而不使用异常或担心异常保证.使用返回码和断言,异常实际上仅限于程序员错误.


Ton*_*roy 5

如果你正在编写代码并达到一个点,你已经确定了一个通常会引发异常的问题,但是希望遵守一些不使用异常的规定,那么你必须找到另一种方法让客户端代码知道错误.

由于许多现有的答案文档,您可以返回一个标记值(例如,真/假成功值,枚举).这种做法在POSIX和libc指定的常见C函数中很常见,如fopen(),strstr()或printf().

另一个重要的选择是设置一些他们可以稍后查询的内部状态.为什么你想要或需要做后者?因为某些函数(主要是C++构造函数和运算符)通常不会为您提供返回错误代码的机会.例如,在:

  X x1(something), x2(whatever);
  fn(x1 + x2);

X::X(...)不能回报任何东西. X::operator+可以调用(假设+未在转换运算符的结果上调用),但fn()可能是期望const X&(或X&&使用C++ 11),并且operator+需要返回一个,X以便它在成功的情况下工作.您没有机会返回不同类型的错误代码. class X可能需要设置一些内部状态,即其他代码(可能是调用fn()后的语句fn())可以测试采取适当的操作.

所以,你最终得到的结果如下:

X x1(something), x2(whatever);
assert(x1.is_valid() and x2.is_valid());
X x3 = x1 + x2;
assert(x3.is_valid());
fn(x3);
Run Code Online (Sandbox Code Playgroud)

请注意,此错误处理约定很冗长,容易被客户端编码器忽略或忽略 - 这是创建异常的原因之一.大多数浮点硬件都使用了一个有趣的变体 - 某些操作,如除以0或低于/溢出,可以将寄存器设置为哨兵值,例如非数字"NaN"或+/-无穷大,然后是涉及参数的操作在这种状态下将状态传播到他们的结果.例如,x = 8 + y / 0; z = x + 2;将设置z也是一个哨兵.这里的想法是您可以编写尽可能计算正确结果的代码,并在使用结果之前检查一次,以查看计算代码中任何位置的错误是否使该结果无效.它有时适用于数学代码,特别是当你没有根据变量的当前值做出分支决策时,但遗憾的是在许多情况下你要么不想要也不想让所有的用户都做出来.可能无效的目标代码超级防御性地处理和传播错误状态.

使用C++没有例外,严重损害了语言的可用性,可维护性,简洁性和优雅性.

作为完全禁止异常使用的替代方法,在某些环境中,您可以捕获API边界处的所有异常,然后以"C"样式返回错误代码或标记值.这允许内部更好的编码,但外部更好的互操作性.遗憾的是,有时使用异常是不切实际的,因为您的代码将在未提供异常处理机制的环境中执行...可能在内核,驱动程序或具有精简C++样式编译器的嵌入式环境中执行.这样的环境不是真正的C++,因为它不符合标准.