【问题标题】:How should errors be handled when creating RAII classes?创建 RAII 类时应该如何处理错误?
【发布时间】:2021-01-01 18:40:04
【问题描述】:

如果构造函数的资源分配部分,例如RAII 套接字包装器失败,我是否只是抛出异常并完成它?或者我应该怎么去std::fstream seems to do it,你需要在构造对象后检查is_open()

前者似乎更符合“资源分配即初始化”的名称,但那为什么标准库基本上让你在使用对象之前检查错误代码?

我指的是basic_fstream 参考页面上的示例(改写,添加了注释):

int main() {
  std::string filename = "test.bin";
  std::fstream s(filename);

  if (!s.is_open()) { // should my socket class require the user to do this?
    std::cout << "failed to open " << filename << '\n';
  } else {
    // ... use it
  }
}

【问题讨论】:

  • 并非所有类型的故障都是平等的。无法打开文件通常是合法且预期的路径,因此您不希望在这种情况下引发异常。
  • 这是课程的马,因为它取决于打开套接字失败的严重程度。如果打开套接字的失败被认为是例行事件,不会影响您的应用程序继续运行的能力,那么就像fstream 那样做。否则抛出异常。如果发生故障并且代码忘记检查(例如使用is_open()),则fstream 方法会出现问题,然后再假设没有发生错误。抛出异常的问题在于它强制调用者响应失败,即使调用者可以安全地忽略它。

标签: c++ raii


【解决方案1】:

推荐的做法是在构造过程中发生任何失败时抛出异常。引用C++ FAQ

问。如何处理失败的构造函数?

A.抛出异常。

构造函数没有返回类型,因此不可能使用返回码。因此,发出构造函数失败信号的最佳方法是抛出异常。如果您没有使用异常的选项,“最坏”的解决方法是通过设置内部状态位将对象置于“僵尸”状态,这样对象的行为就好像它已经死了,即使它是技术上还活着。

如果构造涉及 RAII,则构造函数有额外的责任在抛出之前清理任何已分配的资源。

问。如果我的构造函数可能抛出异常,我应该如何处理资源?

A.对象中的每个数据成员都应该清理自己的烂摊子。

如果构造函数抛出异常,则对象的析构函数不会运行。如果您的对象已经做了一些需要撤消的事情(例如分配一些内存、打开文件或锁定信号量),那么这个“需要撤消的东西”必须由对象内部的数据成员记住。

至于std::fstream构造函数为什么不抛出异常,默认使用fallback选项(把对象变成“僵尸”状态),那是历史和方便的结合,更好在Why are C++ STL iostreams not “exception friendly”? 的答案中进行了解释。

【讨论】:

  • 作为一个这样做的标准库示例,考虑std::lock_guard
  • @DanielH 对。一些容器构造函数也可以抛出像std::bad_alloc 这样的异常。所有这些案例都涉及某种形式的 RAII。
猜你喜欢
  • 2011-01-05
  • 1970-01-01
  • 1970-01-01
  • 2012-10-09
  • 1970-01-01
  • 2014-10-20
  • 2013-01-02
  • 2023-03-07
  • 1970-01-01
相关资源
最近更新 更多