当 ifstream 打开失败时如何获取错误消息

137
ifstream f;
f.open(fileName);

if ( f.fail() )
{
    // I need error message here, like "File not found" etc. -
    // the reason of the failure
}

如何将错误信息作为字符串获取?


3
可能是C++ ifstream错误检查的重复问题。 - Matthieu Rouget
3
@Alex Farber: 当然可以。cerr << "Error code: " << strerror(errno); // Get some info as to why 看起来与问题相关。 - Matthieu Rouget
@MatthieuRouget:请查看我发布的可能重复内容——这似乎是仅由gcc实现的非标准行为。 - arne
1
@MatthieuRouget:strerror(errno)可行。将此发布为答案,我会接受它。 - Alex F
5个回答

98

每个系统调用失败时都会更新errno的值。

因此,当使用类似于以下内容的语句时,您可以获得更多有关ifstream打开失败时发生的情况的信息:

cerr << "Error: " << strerror(errno);

然而,由于每一个系统调用都会更新全局的errno变量,在多线程应用程序中可能会出现问题,如果在执行f.open和使用errno之间另一个系统调用触发错误。

在遵循POSIX标准的系统中:

errno是线程本地的;在一个线程中设置它不会影响其他线程中的值。


编辑(感谢Arne Mertz和其他人的评论):

起初,e.what()似乎是更符合C++惯用法的实现方式,但该函数返回的字符串是与实现相关的,并且(至少在G++的libstdc++中)此字符串没有关于错误原因的有用信息...


2
e.what() 似乎没有提供太多信息,请参见我的答案更新。 - Arne Mertz
20
在现代操作系统中,errno 使用线程本地存储。但是,无法保证在出现 errnofstream 函数不会覆盖 errno。底层函数可能根本不设置 errno(在Linux上进行直接系统调用或Win32),这在许多实际实现中并不奏效。 - strcat
3
在MSVC中,e.what()总是输出相同的消息"iostream stream error"。 - rustyx
warning C4996: 'strerror': This function or variable may be unsafe. Consider using strerror_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details. 1> C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\string.h(168) : see declaration of 'strerror' - sergiol
3
@sergiol 那些都是谎言。请忽略它们或关闭警告。 - S.S. Anne

38

您可以尝试让流在失败时抛出异常:

std::ifstream f;
//prepare f to throw if failbit gets set
std::ios_base::iostate exceptionMask = f.exceptions() | std::ios::failbit;
f.exceptions(exceptionMask);

try {
  f.open(fileName);
}
catch (std::ios_base::failure& e) {
  std::cerr << e.what() << '\n';
}

e.what() 似乎并没有什么帮助:

  • 我在 Win7、Embarcadero RAD Studio 2010 上尝试了它,得到了 "ios_base::failbit set" 的错误,而 strerror(errno) 则返回 "No such file or directory"。
  • 在 Ubuntu 13.04、gcc 4.7.3 上,异常显示为 "basic_ios::clear"(感谢 arne)。

如果 e.what() 对你没有用(因为它没有标准化),可以尝试使用 std::make_error_condition(仅限 C++11):

catch (std::ios_base::failure& e) {
  if ( e.code() == std::make_error_condition(std::io_errc::stream) )
    std::cerr << "Stream error!\n"; 
  else
    std::cerr << "Unknown failure opening file.\n";
}

谢谢。我没有测试这个,因为评论中发布的strerror(errno)可以工作并且非常简单易用。我认为e.what会起作用,因为errno可以工作。 - Alex F
2
@AlexFarber:我认为Arne的答案比我的更好。我的解决方案不是解决你的问题的C++方式。然而,我没有找到关于C++库如何将系统调用错误映射到exception.what()的官方信息。也许这是一个深入研究libstdc++源代码的好机会 :-) - Matthieu Rouget
1
我尝试了一下:试图打开一个不存在的文件,异常消息显示为 basic_ios::clear,没有其他信息。这并不是很有帮助。这就是为什么我没有发布 ;) - arne
@arne 你使用的是哪个平台、编译器和操作系统? - Arne Mertz
在C++20中,即使文件成功打开,这也会抛出异常。 - Richard Neumann
显示剩余4条评论

29

继@Arne Mertz的回答之后,从C++11开始std::ios_base::failure继承自system_error(参见http://www.cplusplus.com/reference/ios/ios_base/failure/),其中包含了错误代码和strerror(errno)返回的消息。

std::ifstream f;

// Set exceptions to be thrown on failure
f.exceptions(std::ifstream::failbit | std::ifstream::badbit);

try {
    f.open(fileName);
} catch (std::system_error& e) {
    std::cerr << e.code().message() << std::endl;
}

如果fileName不存在,这将打印No such file or directory.


16
在我的 MSVC 2015 中,只是打印了 "iostream 流错误"。 - rustyx
7
对我来说,GCC 6.3也会打印出“iostream错误”。你测试的是哪个编译器?是否有任何编译器实际上提供了用户可读的失败原因? - Ruslan
2
在 macOS 上使用 libc++ 的 Clang 6:未指定 iostream_category 错误 - akim
Xcode 10.2.1(Clang)/ libc++(C++17)在MacOS 10.14.x上:还有“未指定的iostream_category错误”。 strerror(errno)似乎是唯一正确的方法。我想我可以首先通过询问std::filesystem是否存在路径,并检查它返回的std::error_code来捕获它。 - SMGreenfield
在示例程序中,语句 f.open(fileName) 抛出了一个类型为 std::ios_base::failure 的异常,该异常派生自 std::system_error。异常被 catch 块捕获。在 catch 块内,e.code() 调用 std::ios_base::failure::code(),返回一个类型为 std::error_code 的对象。类 std::error_code 定义的错误代码是 平台相关 的--即 e.code().message()e.code().value() 都返回平台相关的值。 - Jim Fischer

16

您还可以像下面的测试代码中所示一样抛出std::system_error。这种方法似乎比f.exception(...)产生更可读的输出。

#include <exception> // <-- requires this
#include <fstream>
#include <iostream>

void process(const std::string& fileName) {
    std::ifstream f;
    f.open(fileName);

    // after open, check f and throw std::system_error with the errno
    if (!f)
        throw std::system_error(errno, std::system_category(), "failed to open "+fileName);

    std::clog << "opened " << fileName << std::endl;
}

int main(int argc, char* argv[]) {
    try {
        process(argv[1]);
    } catch (const std::system_error& e) {
        std::clog << e.what() << " (" << e.code() << ")" << std::endl;
    }
    return 0;
}

示例输出(在安装有clang的Ubuntu上):

$ ./test /root/.profile
failed to open /root/.profile: Permission denied (system:13)
$ ./test missing.txt
failed to open missing.txt: No such file or directory (system:2)
$ ./test ./test
opened ./test
$ ./test $(printf '%0999x')
failed to open 000...000: File name too long (system:36)

1
上面的std::system_error示例略有不准确。 std::system_category()将从系统的本机错误代码库映射错误代码。对于*nix,这是errno。对于Win32,它是GetLastError()。即,在Windows上,上面的示例将打印
failed to open C:\path\to\forbidden: The data is invalid

由于EACCES是13,这是Win32错误代码ERROR_INVALID_DATA。

要修复它,可以使用系统的本机错误代码功能,例如在Win32上。

throw new std::system_error(GetLastError(), std::system_category(), "failed to open"+ filename);

或者使用 errno 和 std::generic_category(),例如。
throw new std::system_error(errno, std::generic_category(), "failed to open"+ filename);

请注意,除非您的框架要求您这样做,否则不要抛出原始指针。使用throw std::make_shared<std::system_error>(...),或者更好地使用throw std::system_error(...) - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接