c++ - C++ 社区是否就何时应使用异常达成普遍共识?

标签 c++ exception error-handling exception-handling

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

  1. 对错误代码使用异常
  2. 大部分时间使用错误代码,仅在发生某些灾难性错误时才异常(exception)

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

最佳答案

正如您可能从大量答案中得出的结论,肯定没有共识。

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

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

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

另一方面:

  • 签名中完美记录了错误的可能性
  • 异常在代码检查中沉默(阅读 GotW #20: Code Complexity 并哭泣),而隐藏执行路径使推理更加困难。

这两种解决方案都显得笨拙的原因仅仅是错误检查很困难。事实上,我每天编写的大部分代码都与错误检查有关,无论是技术上的还是功能上的。

那该怎么办?

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

我个人喜欢在这里利用类型系统。典型的例子是指针引用二分法:指针就像一个引用,可以为空(并且可以重新定位,但在这里无关紧要)

因此,而不是:

// 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);

我会倾向于写:

Object const* Container::search(Key const& key) const;

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

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);

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

  • 如果我们正在搜索一个对象,那么找不到它既是一种非常常见的情况,也没有太多数据可以携带:错误原因? 它不存在
  • 客户不一定认为它不存在是一个错误,我有什么资格认为我比她更了解她的业务?我是谁来决定永远不会出现不找到所要求的东西不合适的情况?

我对异常本身没有问题,但它们会使代码变得笨拙,请考虑:

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
}

并将其与“异常”情况进行比较:

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
}

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

另一方面:

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

当然比:

if (!print("My cute little baby ")) { /*deal*/ }
if (!print(baby.name())) { /*deal*/ }
if (!print(" weighs ")) { /*deal*/ }
if (!print(baby.weight())) { /*deal*/ }

那么什么是最好的呢?

这取决于。像所有工程问题一样,没有 Elixir ,一切都是让步。

所以请记住两件事:

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

如果您发现自己想知道是否使用异常,只需尝试使用您的 API。如果没有明确的赢家,那就是:没有理想的解决方案。

哦,当发现在设计时选择的错误报告机制不再合适时,请毫不犹豫地重构您的 API。不要害臊:需求会随着时间而变化,所以 API 也会随之变化。

个人我倾向于只对不可恢复的错误使用异常:因此我的代码中很少尝试/捕获,仅在最外层,以准确记录错误(爱堆栈帧)并记录以及 BOM 的转储。

这与 Haskell 非常相似(并且确实受到了强烈的影响),那里的代码被隔离在两个明确的部分中:虽然任何都可以抛出异常,但只有 IO 部分(外部部分)可以真正捕获它们。因此,纯部分必须以其他方式处理错误情况,以防它们“正常”。

但是,如果我遇到一个问题,即使用异常使代码更易于阅读且更自然(这是主观的),那么我使用异常:)

关于c++ - C++ 社区是否就何时应使用异常达成普遍共识?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5609503/

相关文章:

c++ - 交换宏的值

Java 异常处理(用户输入中的空格)

sockets - 为什么写入未连接的套接字会先发送 SIGPIPE?

java - java.net.SocketTimeoutException 中 "Connect timed out"和 "failed to connect to"之间的确切区别是什么?

python - 如果成功,否则在python中引发异常时,返回true是否是一种好习惯?

wordpress - 更改名称服务器后出现 500 HTTP 错误 (WORDPRESS)

c++ - 将 udp 数据包从 WAN 发送到本地计算机,而无需在路由器 C++ 上设置端口转发

c++ - 对自身的不明确函数调用

c++ - 如何将字符串与 chrono::milliseconds 连接起来?

android - 选择 Android Spinner 会导致 WindowManager$BadTokenException