在C++社区中是否存在关于何时应该使用异常的一般共识?

pep*_*psi 33 c++ error-handling exception-handling exception

我花了几个小时阅读关于何时使用例外的SO问题,似乎有两个阵营有不同的观点:

  1. 使用异常而非错误代码
  2. 大多数情况下使用错误代码,仅在发生某些灾难性错误时使用异常

这只是一个有争议的话题,没有被广泛接受的最佳实践吗?

Mat*_* M. 13

你可以从丰富的答案中找到答案,但肯定没有达成共识.

从语义上讲,异常和错误提供了完全相同的功能.事实上,它们在所有语义方面都是相同的,并且错误可以像异常一样被任意丰富(您不必使用简单的代码,您可以使用真正的数据包!).

唯一的区别是它们的传播方法:

  • 错误必须手动传递下来
  • 异常会自动传播

另一方面:

  • 签名中完整记录了错误的可能性
  • 代码检查中的异常是静默的(读取GotW#20:代码复杂性和哭泣),隐藏的执行路径使推理更难.

这两种解决方案看起来很笨拙的原因很简单就是错误检查很困难.事实上,我每天写的大部分代码都涉及错误检查,无论是技术还是功能.

那么该怎么办 ?

警告:提前演示,如果您只关心答案,请跳到下一部分

我个人喜欢在这里利用类型系统.典型的例子是指针引用二分法:指针就像一个可以为null的引用(并重新设置,但这里没关系)

因此,而不是:

// Exceptions specifications are better not used in C++
// Those here are just to indicate the presence of exceptions
Object const& Container::search(Key const& key) const throw(NotFound);
Run Code Online (Sandbox Code Playgroud)

我倾向于写:

Object const* Container::search(Key const& key) const;
Run Code Online (Sandbox Code Playgroud)

或者更好的是,使用聪明的指针:

Pointer<Object const> Container::search(Key const& key) const;

template <typename O>
O* Pointer<O>::operator->() const throw(Null);

template <typename O>
O& Pointer<O>::operator*() const throw(Null);
Run Code Online (Sandbox Code Playgroud)

在这里,我发现使用异常是多余的,原因有两个:

  • 如果我们正在搜索一个对象,那么没有发现它是一个非常常见的事情并且没有太多数据可以携带:错误原因?它不在那里
  • 客户并不一定认为它不存在错误,我应该假设我比她更了解自己的业务?我是谁来决定永远不会出现不适合不找到要求的情况?

我本身没有异常问题,但是它们会使代码变得尴尬,请考虑:

void noExceptions(Container const& c)
{
  Pointer<Object const> o = c.search("my-item");

  if (!o) {
    o = c.search("my-other-item");
  }

  if (!o) { return; } // nothing to be done

  // do something with o
}
Run Code Online (Sandbox Code Playgroud)

并将其与"例外"案例进行比较:

void exceptions(Container const& c)
{
  Object const* p = 0;
  try {
    p = &c.search("my-item");
  }
  catch(NotFound const&) {
    try {
      p = &c.search("my-other-item");
    }
    catch(NotFound const&) {
      return; // nothing to be done
    }
  }

  // do something with p
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,使用异常似乎不合适:/

另一方面:

try {
 print() << "My cute little baby " << baby.name() << " weighs " << baby.weight();
}
catch(Oupsie const&) {
  // deal
}
Run Code Online (Sandbox Code Playgroud)

肯定比以下更吸引人:

if (!print("My cute little baby ")) { /*deal*/ }
if (!print(baby.name())) { /*deal*/ }
if (!print(" weighs ")) { /*deal*/ }
if (!print(baby.weight())) { /*deal*/ }
Run Code Online (Sandbox Code Playgroud)

什么是最好的呢?

这取决于.像所有工程问题一样,没有银弹,这都是关于让步的.

所以记住两件事:

  • 错误报告是API的一部分
  • API的设计应考虑到易用性

如果您发现自己想知道是否使用例外,请尝试使用您的API.如果没有明确的赢家,那就是:没有理想的解决方案.

哦,当很明显在制作它时选择的错误报告机制不再合适时,不要犹豫重构你的API.不要感到羞耻:需求随时间而变化,因此API随之变化是正常的.

就个人而言,我倾向于仅对不可恢复的错误使用异常:因此我的代码中只有少量try/catch,只有在最外层,才能准确记录错误(爱堆栈帧)并记录BOM的转储.

这与Haskell非常相似(并且确实受到强烈影响),代码分为两个明确的部分:虽然任何可以抛出异常,但只有IO部分(外部部分)可能实际捕获它们.因此,纯部分必须以其他方式处理错误条件,以防它们"正常".

但是,如果我遇到一个问题,使用异常使代码更容易阅读和更自然(这是主观的)然后我使用异常:)


Bri*_*lly 10

我认为这不是C++社区独有的讨论,但这里有两个高级指南对我有所帮助:

  1. 仅在特殊情况下抛出异常.这听起来很明显,但许多API构建时会抛出大约50%的异常被抛出(并且布尔返回状态更合适).
  2. 在catch子句中,知道何时消耗异常,何时按原样重新抛出它们以及何时抛出不同类型的异常.我无法为你提供一个通用的规则,因为它非常依赖于你的应用程序的需求,但是如果你从这个方面拿走一件事,它应该是异常的默认消费可能是最糟糕的事情你的代码可以做到.每个框架都应该知道在出现问题时调用框架的预期.


Lar*_*abe 8

异常比错误代码更容易使用,因为它们可以被深度嵌套的子例程抛出,并且仅在可以处理它的级别被截获.

错误代码需要向上传递,因此调用另一个函数的每个函数都必须将错误代码传递回其调用者.

错误代码与异常没有任何明显的功能优势,因为异常可以在其中捆绑错误代码.可能有一段时间错误代码可能比异常更有效,但我认为额外代码的成本和维护它的难度超过了那里的任何可能的优势.

但是,许多应用程序中存在错误代码,因为它们是在没有异常的语言中编写或移植的,因此继续使用统一的错误处理方法是有意义的.

  • @pepsi:的确如此.同样,我尝试将*error*代码与mere*result*代码区分开来.如果函数专门执行可能失败的操作,例如`connect`到套接字,则应该通过代码而不是异常来指示失败.稍后,如果套接字在读取时意外关闭,我希望这是一个例外,因为失败的深层原因与手头的任务无关,不太可能发生,与立即调用代码的关联性较小. (5认同)
  • 频率和误差传播应该是主要的决定因素.正如Potatoswatter所说,如果一个函数经常失败,调用者必须*总是*处理这种情况,那么错误代码可能是好的.在写案例中,通常唯一合理的处理程序在调用图中处于高位,因此传播返回代码既困难又可能丢失异常可能携带的信息. (3认同)

And*_*adt 8

不,没有达成共识.

编辑:正如您所看到的,从其他答案来看,没有达成共识 - 只有思想流派,设计原则和行动计划.没有任何计划完全适合所有情况.

  • 这不是一个真正的答案. (6认同)