How to get error message when ifstream open fails

ifstream f;
f.open(fileName);


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

How to get error message as string?

147272 次浏览

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

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()似乎帮助不大:

  • 我在 Embarcadero RAD Studio 2010的 Win7上尝试过,它给出的是“ 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";
}

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

因此,您可以通过以下方法获得更多关于当 ifstream open 失败时会发生什么情况的信息:

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

但是,由于 每个系统调用更新了全局 errno值,因此如果另一个系统调用在执行 f.open和使用 errno之间触发错误,那么在多线程应用程序中可能会出现问题。

关于 POSIX 标准的系统:

errno is thread-local; setting it in one thread does not affect its 任何其他线程中的值。


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

一开始,e.what()似乎是一种更为 C + + 风格的实现方式,但是这个函数返回的字符串依赖于实现,(至少在 G + + 的 libstdc + + 中)这个字符串没有关于错误背后原因的有用信息..。

在@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.

您还可以抛出 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;
}

示例输出(Ubuntu w/clang) :

$ ./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)

上面的 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);