为什么 C++ STL iostreams 不“异常友好”?

Why are C++ STL iostreams not "exception friendly"?

提问人:Roddy 提问时间:7/5/2010 最后编辑:Roddy 更新时间:1/13/2021 访问量:22765

问:

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

两个问题...

答:为什么会这样 - 对于一个从第一天起就有异常的语言来说,这似乎是一个奇怪的设计决定?

b:如何最好地避免这种情况?我可以按照我的预期制作垫片类,但这感觉就像重新发明轮子。也许有一个 BOOST 库可以以更理智的方式做到这一点?

C++ 异常 STL IOstream

评论

2赞 Clifford 5/31/2017
iostream 是 C++ 标准库的一部分,STL 是 C++ 标准库的子集,但 iostream 不是 STL 子集的一部分。

答:

76赞 kennytm 7/5/2010 #1
  1. C++ 从第一天起就没有例外。“C with classes”始于 1979 年,并于 1989 年添加了例外。同时,该库早在 1984 年就编写了(后来在 1989 年成为(后来在 1991 年由 GNU 重新实现)),它只是不能在一开始就使用异常处理。streamsiostreams

    裁判:

  2. 您可以使用 .exceptions 方法启用例外。

// 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";
    }
}

评论

9赞 Roddy 7/5/2010
file.close()- 你需要吗?我以为他们足够聪明,可以接近毁灭......???
2赞 7/5/2010
这个例子有点蹩脚。如果启用了 eof 异常,为什么要(错误地)测试 eof?
5赞 7/5/2010
@Roddy close() 将由流 destuctor 调用。但是,明确说出您的意思总是一个好主意。
23赞 Roddy 7/5/2010
@Neil。谢谢 - 但不同意明确关闭()ing - 这就像显式删除 autoptr 对象一样!
18赞 Yakov Galka 3/24/2012
@Roddy:是的,他们会在破坏时关闭自己,但他们也会抓住所有可能被 抛出的异常。如果是日志文件,那就没问题了。如果是文档的“保存”命令,那么你真的需要确保文件已关闭,如果刷新失败,请将其报告给用户。 流就像提交一个事务,或者像 Copy&Swap Assignment Operator 实现中的 ing。此“提交”步骤在 C++ 中很常见。flush()closing()swap()
2赞 Neel Basu 7/5/2010 #2
  1. 每当抛出异常时,都需要考虑异常安全性。所以没有例外,没有例外,没有例外——安全令人头疼。

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

  3. Iostreams 允许你接受异常和无期望行为。

评论

4赞 Triskeldeian 9/2/2019
恕我直言,第 1 点有点毫无意义。没有例外,你必须注意“错误安全”,在许多情况下,它比“异常安全”要混乱得多,因为它没有那么整齐地编码
3赞 Triskeldeian 9/3/2019
投掷不会导致突然关机。不抓住他们。如果您有错误代码并且忽略它们,则可能会遇到同样的麻烦,并且当您继续对垃圾输入进行操作时,可能会遇到更糟糕的问题
2赞 Triskeldeian 9/4/2019
异常安全与是否捕获异常无关。它告诉您一旦失败,您应该从失败的对象中得到什么。即使通过错误代码传达故障,也可以应用相同的类别。
2赞 Triskeldeian 9/4/2019
忽略错误是不好的,无论您是通过错误代码还是异常来传达它们。在异常的情况下,如果用户没有抓住它们,系统会非常清楚地告诉你,你正在做非常错误的事情。使用错误代码,它可能会失败,直到您真正需要这些数据,在我看来,不可靠的结果比崩溃更糟糕
1赞 Triskeldeian 9/4/2019
显然你没有在阅读。异常安全与异常处理不是一回事。第一个是指开发人员向用户提供有关您创建的类的保证,第二个是指处理异常情况的过程。你谈论异常处理,并称之为异常安全。
3赞 anon 7/5/2010 #3

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

评论

2赞 Roddy 7/5/2010
“某种恢复风格的编程”——我不确定你的意思——我经常有类似的东西while(!completed) {try { doIo();completed=true;} catch (...) { if (promptAbortRetry("whoops!") == ABORT) completed = true;}
2赞 7/5/2010
@Roddy 我所说的恢复是指有时有必要尝试以一种方式读取值,检测故障,然后尝试以另一种方式读取它。如果使用例外,则更难。
1赞 Roddy 7/5/2010
@Neil - 谢谢,很有道理。老实说,我没有考虑格式转换异常:我主要关心的是文件系统级别的异常(找不到文件、磁盘已满、whathaveyou)
5赞 Roddy 7/6/2010 #4

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

首先,感谢 KennyTM 的历史。正如他所说,C++从第一天起就没有例外,所以iostreams的“异常”处理后来被附加也就不足为奇了。

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

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

#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;
}

我认为在这个版本中,应该抛出“找不到文件”之类的东西,但是“istream”错误将由 badbit/failbit 报告。