【问题标题】:Why are C++ STL iostreams not "exception friendly"?为什么 C++ STL iostreams 不是“异常友好的”?
【发布时间】:2023-04-01 11:17:02
【问题描述】:

我习惯了 Delphi VCL 框架,其中 TStreams 会在错误时抛出异常(例如,找不到文件,磁盘已满)。我正在移植一些代码以改用 C++ STL,并且已被 iostreams 捕获,默认情况下不抛出异常,而是设置 badbit/failbit flags

两个问题...

a:为什么会这样 - 对于从一开始就包含异常的语言来说,这似乎是一个奇怪的设计决定?

b:如何最好地避免这种情况?我可以生成像我期望的那样抛出的 shim 类,但这感觉就像重新发明轮子。也许有一个 BOOST 库可以更明智地做到这一点?

【问题讨论】:

  • iostream 是 C++ 标准库的一部分,STL 是 C++ 标准库的子集,但 iostream 不是 STL 子集的一部分。

标签: c++ exception stl iostream


【解决方案1】:
  1. C++ 从第一天开始就没有例外。 “带类的 C”始于 1979 年,并在 1989 年添加了异常。同时,streams 库早在 1984 年编写(后来在 1989 年成为iostreams(后来在 1991 年由 GNU 重新实现)),它只是不能一开始就使用异常处理。

    参考:

  2. 可以使用the .exceptions method 启用异常。

// ios::exceptions
#include <iostream>
#include <fstream>
#include <string>

int main () {
    std::ifstream file;
    file.exceptions(ifstream::failbit | ifstream::badbit);
    try {
        file.open ("test.txt");
        std::string buf;
        while (std::getline(file, buf))
            std::cout << "Read> " << buf << "\n";
    }
    catch (ifstream::failure& e) {
        std::cout << "Exception opening/reading file\n";
    }
}

【讨论】:

  • file.close() - 你需要吗?我原以为他们足够聪明,可以关闭破坏……???
  • 这个例子有点蹩脚。如果您启用了 eof 异常,为什么要(错误地)测试 eof?
  • @Roddy close() 将由流析构函数调用。但是,明确说出您的意思总是一个好主意。
  • @Neil。谢谢 - 但不同意明确关闭()ing - 这就像明确删除 autoptr 对象!
  • @Roddy:是的,它们会在销毁时关闭自己,但它们也会捕获所有可能由flush() 抛出的异常。如果它是一个日志文件,那很好。如果它是一个文档“保存”命令,那么您真的想确保文件已关闭,并且如果刷新失败将其报告给用户。 closing() 流就像提交事务,或者就像复制和交换赋值运算符实现中的 swap()ing。这个“提交”步骤在 C++ 中很常见。
【解决方案2】:

好的,现在是“回答我自己的问题”时间...

首先,感谢 KennyTM 的历史。正如他所说,C++ 从第一天开始就没有设计了异常,因此 iostreams 的“异常”处理后来被用螺栓固定也就不足为奇了。

其次,正如 Neil B 指出的那样,输入格式转换错误出现异常将是一个巨大的痛苦。这让我感到惊讶,因为我将 iostreams 视为一个简单的文件系统包装层,而我根本没有考虑过这种情况。

第三,BOOST 似乎确实为聚会带来了一些东西:Boost.IOStreams。如果我理解正确,这些处理流的低级 I/O 和缓冲方面,让常规 c++ IOStreams 库来处理转换问题。 Boost.IOStreams does use exceptions 以我期望的方式。如果我理解正确的话,肯尼的例子也可能是这样的:

#include <ostream>
#include <boost/iostreams/device/file.hpp>
#include <boost/iostreams/stream.hpp>

int main () {
  boost::iostreams::stream_buffer <boost::iostreams::file_source> buf("test.txt");
  std::istream file(&buf);

  try {
    std::string buf;
    while (std::getline(file, buf))
      std::cout << "Read> " << buf << "\n";
  }
  catch (std::ios_base::failure::failure e) {
    std::cout << "Exception opening/reading file\n";
  }
  std::cout.flush();

  file.close();

  return 0;
}

认为在这个版本中,应该会抛出“找不到文件”之类的问题,但 badbit/failbit 会报告“istream”错误。

【讨论】:

    【解决方案3】:

    正如 Kenny 所说,您可以根据需要启用例外。但是通常 I/O 在发生错误时需要某种恢复风格的编程,这不容易通过使用异常来支持 - 在输入操作之后测试流的状态要简单得多。我实际上从未见过任何在 I/O 上使用异常的 C++ 代码。

    【讨论】:

    • “某种恢复风格的编程” - 我不确定你的意思 - 我经常有类似while(!completed) {try { doIo();completed=true;} catch (...) { if (promptAbortRetry("whoops!") == ABORT) completed = true;}的东西
    • @Roddy 恢复我的意思是有时需要尝试以一种方式读取值,检测失败,然后尝试以另一种方式读取它。如果使用异常,这就更难了。
    • @Neil - 谢谢,很有道理。老实说,我没有考虑格式转换异常:我主要关注文件系统级别的异常(文件未找到、磁盘已满、whathaveyou)
    【解决方案4】:
    1. 每当您抛出异常时,您都需要考虑异常安全性。所以没有异常,没有异常,没有异常安全问题。

    2. Iostreams 也支持异常。但是抛出异常是可选的。您可以通过设置exceptions (failbit | badbit | eofbit)

    3. 来启用异常
    4. Iostreams 让您可以处理异常和无预期行为。

    【讨论】:

    • 第 1 点有点没有意义恕我直言。没有例外,您必须注意“错误安全”,这在许多情况下比“异常安全”要混乱得多,因为它没有被整齐地编码
    • 投掷不会导致突然关机。不抓住他们会。如果您有错误代码并且您忽略它们,那么当您继续对垃圾输入进行操作时,您可能会遇到同样的麻烦,并且可能会更糟
    • 异常安全与是否捕获异常无关。它告诉您一旦失败,您应该对失败的对象有什么期望。即使您通过错误代码传达您的失败,您也可以应用相同的类别。
    • 忽略错误是不好的,无论您是通过错误代码还是异常来传达它们。在异常的情况下,如果用户没有捕捉到它们,系统会非常清楚地告诉你你做错了什么。使用错误代码,它可能会在您真正需要这些数据之前在您没有注意到的情况下失败,并且在我看来,不可靠的结果比崩溃更糟糕
    • 显然您没有在阅读。异常安全与异常处理不同。第一个是指保证您的开发人员向用户提供有关您创建的类的信息,第二个是指处理异常情况的程序。您谈论异常处理并称之为异常安全。
    猜你喜欢
    • 1970-01-01
    • 2011-06-20
    • 1970-01-01
    • 1970-01-01
    • 2016-07-03
    • 2010-12-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多