在 C++ 中读取文件并处理可能的错误的便携式方法
Posted
技术标签:
【中文标题】在 C++ 中读取文件并处理可能的错误的便携式方法【英文标题】:Portable way to read a file in C++ and handle possible errors 【发布时间】:2017-08-23 13:09:39 【问题描述】:我想做一个简单的事情:从文件中读取第一行,并在没有这样的文件、没有读取文件的权限等情况下进行适当的错误报告。
我考虑了以下选项:
std::ifstream
。不幸的是,没有可移植的方法来报告系统错误。其他一些答案建议在阅读失败后检查errno
,但标准不保证errno
是由iostreams 库中的任何函数设置的。
C 风格fopen
/fread
/fclose
。这可行,但不如带有std::getline
的iostreams 方便。我正在寻找 C++ 解决方案。
有没有什么方法可以使用 C++14 和 boost 来实现这一点?
【问题讨论】:
是的,有一种方法可以“使用 C++14 完成此任务”。实现你自己的std::streambuf
子类,然后用它来构造一个std::istream
。你得到一个普通的流,你的实现可以使用操作系统调用来打开/读取文件,并报告实际的操作系统错误。
好吧,如果一个人的目标是捕获底层操作系统错误,并且仍然有一个普通的std::istream
,那么这是唯一的方法。不幸的是,C++ 并没有以拥有大量魔术按钮而闻名,人们只需按下这些按钮即可获得他们正在寻找的结果。许多事情需要大量工作才能正确、正确地实施。
@ArtemyVysotsky 这完全不合适,因为它引入了 TOCTTOU 问题。
@OlegAndriyanov 我相信更广泛使用的术语是“比赛条件”(我不得不谷歌 TOCTTOU 才能得到它)
关于“C 做得更好”。 C 标准严格要求三个与 errno 相关的宏:EDOM、EILSEQ 和 ERANGE,并且不保证任何 I/O 函数将 errno 设置为特定的任何内容。
【参考方案1】:
免责声明:我是 AFIO 的作者。但您正在寻找的正是https://ned14.github.io/afio/,它是结合了 2015 年 8 月 Boost 同行评审反馈的 v2 库。See the list of features here。
我当然会警告说这是一个 alpha 质量库,您不应该在生产代码中使用它。但是,已经有不少人这样做了。
如何使用AFIO解决OP的问题:
请注意,AFIO 是一个非常低级的库,因此您必须键入更多代码才能实现与 iostream 相同的功能,另一方面,您不会获得内存分配、不会抛出异常、不会出现不可预测的延迟峰值:
// Try to read first line from file at path, returning no string if file does not exist,
// throwing exception for any other error
optional<std::string> read_first_line(filesystem::path path)
using namespace AFIO_V2_NAMESPACE;
// The result<T> is from WG21 P0762, it looks quite like an `expected<T, std::error_code>` object
// See Outcome v2 at https://ned14.github.io/outcome/ and https://lists.boost.org/boost-announce/2017/06/0510.php
// Open for reading the file at path using a null handle as the base
result<file_handle> _fh = file(, path);
// If fh represents failure ...
if(!_fh)
// Fetch the error code
std::error_code ec = _fh.error();
// Did we fail due to file not found?
// It is *very* important to note that ec contains the *original* error code which could
// be POSIX, or Win32 or NT kernel error code domains. However we can always compare,
// via 100% C++ 11 STL, any error code to a generic error *condition* for equivalence
// So this comparison will work as expected irrespective of original error code.
if(ec == std::errc::no_such_file_or_directory)
// Return empty optional
return ;
std::cerr << "Opening file " << path << " failed with " << ec.message() << std::endl;
// If errored, result<T>.value() throws an error code failure as if `throw std::system_error(fh.error());`
// Otherwise unpack the value containing the valid file_handle
file_handle fh(std::move(_fh.value()));
// Configure the scatter buffers for the read, ideally aligned to a page boundary for DMA
alignas(4096) char buffer[4096];
// There is actually a faster to type shortcut for this, but I thought best to spell it out
file_handle::buffer_type reqs[] = buffer, sizeof(buffer);
// Do a blocking read from offset 0 possibly filling the scatter buffers passed in
file_handle::io_result<file_handle::buffers_type> _buffers_read = read(fh, reqs, 0);
if(!_buffers_read)
std::error_code ec = _fh.error();
std::cerr << "Reading the file " << path << " failed with " << ec.message() << std::endl;
// Same as before, either throw any error or unpack the value returned
file_handle::buffers_type buffers_read(_buffers_read.value());
// Note that buffers returned by AFIO read() may be completely different to buffers submitted
// This lets us skip unnecessary memory copying
// Make a string view of the first buffer returned
string_view v(buffers_read[0].data, buffers_read[0].len);
// Sub view that view with the first line
string_view line(v.substr(0, v.find_first_of('\n')));
// Return a string copying the first line from the file, or all 4096 bytes read if no newline found.
return std::string(line);
【讨论】:
您能否提供一个使用 AFIO 从文件中读取一行的代码示例?这看起来像我需要的,但我还没有找到任何代码示例。 @OlegAndriyanov 这是个好主意。几分钟后回来。 @OlegAndriyanov 添加的使用代码示例。如果您有任何问题,请告诉我。 这看起来像是围绕操作系统调用的非常好的包装器。是否有将 AFIO 纳入 boost 的计划?此外,IMO 最好在 AFIO 中实现std::streambuf
(正如 @SamVarshavchik 建议的那样)以重用格式化 iostream 的 IO 操作的所有功能,但具有可移植的错误处理。还是超出了图书馆的范围?
@OlegAndriyanov 我希望在 2019 年返回 AFIO 进行第二次 Boost 同行评审。我在 Coroutines TS、Ranges TS、Expected 和 Span 提案以及 C++ 20 中的其他一些内容上受阻。无需添加 streambuf 支持,AFIO 已经使用 Ranges TS。所以用 Ranges 说 i/o,一切都很好。 Eric 在ericniebler.com/2017/08/17/… 上写了一篇关于此的博客文章,他使用 libuv 而不是 AFIO,因为它更广为人知。但是 AFIO远比 libuv 更高效,WG21 目前正在为 iostreams v2 的 AFIO 微笑【参考方案2】:
boost-users 邮件列表中的人指出,boost.beast 库具有独立于操作系统的 API,用于基本文件 IO包括正确的错误处理。开箱即用的file concept 有三种实现:POSIX、stdio 和 win32。这些实现支持 RAII(销毁时自动关闭)和移动语义。 POSIX 文件模型自动处理EINTR
错误。基本上,这对于可移植地逐块读取文件是足够且方便的,例如,显式处理文件不存在的情况:
using namespace boost::beast;
using namespace boost::system;
file f;
error_code ec;
f.open("/path/to/file", file_mode::read, ec);
if(ec == errc::no_such_file_or_directory)
// ...
else
// ...
【讨论】:
【参考方案3】:最好的办法是包装 Boost WinAPI 和/或 POSIX API。
“天真的”C++ 标准库的东西(花里胡哨)不会让你走得太远:
Live On Coliru
#include <iostream>
#include <fstream>
#include <vector>
template <typename Out>
Out read_file(std::string const& path, Out out)
std::ifstream s;
s.exceptions(std::ios::badbit | std::ios::eofbit | std::ios::failbit);
s.open(path, std::ios::binary);
return out = std::copy(std::istreambuf_iterator<char>s, , out);
void test(std::string const& spec) try
std::vector<char> data;
read_file(spec, back_inserter(data));
std::cout << spec << ": " << data.size() << " bytes read\n";
catch(std::ios_base::failure const& f)
std::cout << spec << ": " << f.what() << " code " << f.code() << " (" << f.code().message() << ")\n";
catch(std::exception const& e)
std::cout << spec << ": " << e.what() << "\n";
;
int main()
test("main.cpp");
test("nonexistent.cpp");
打印...:
main.cpp: 823 bytes read
nonexistent.cpp: basic_ios::clear: iostream error code iostream:1 (iostream error)
当然,您可以通过阅读
<filesystem>
添加更多诊断信息,但是 如前所述,这很容易受到竞争的影响(取决于您的应用程序,这些甚至可能会引发安全漏洞,所以只需说“不”)。使用
boost::filesystem::ifstream
不会改变引发的异常更糟糕的是,使用 Boost IOstream 无法引发任何错误:
template <typename Out> Out read_file(std::string const& path, Out out) namespace io = boost::iostreams; io::stream<io::file_source> s; s.exceptions(std::ios::badbit | std::ios::eofbit | std::ios::failbit); s.open(path, std::ios::binary); return out = std::copy(std::istreambuf_iterator<char>s, , out);
打印愉快:
main.cpp: 956 bytes read nonexistent.cpp: 0 bytes read
Live On Coliru
【讨论】:
这些例子有什么用?证明他们没有做我想做的事?我不认为手动 API 包装是一个好主意,因为这个任务很容易用任何现代语言在几行中解决。甚至 C 也比 C++ 做得更好。 信息就是一切。如果你知道,你可以在你的问题中展示它。另外,I share the rant,但这不是一个有用的答案。 (这甚至是一种可疑的态度)。 感谢分享。 boost 包装ifstream
和 ofstream
并处理正确的路径,因此您在两个平台上都有 unicode
老实说,我认为这个答案提出的研究可能是问题的一部分。因为它不是,所以我在这里将其作为一项工作来展示,以避免其他人不得不尝试同样的事情。这些部分并没有很好的记录(这就是问题如此相关的原因)。【参考方案4】:
#include <iostream>
#include <fstream>
#include <string>
#include <system_error>
using namespace std;
int
main()
ifstream f("testfile.txt");
if (!f.good())
error_code e(errno, system_category());
cerr << e.message();
//...
// ...
ISO C++ 标准:
标题的内容 “赛尔诺” 与 POSIX 标头相同 “错误号.h” , 除了那个 错误号 将 被定义为宏。 [ 笔记: 目的是与 POSIX 标准保持紧密一致。 - 结尾 笔记 ] 独立 错误号 应为每个线程提供值。
【讨论】:
检查errno
不像我说的那样可移植。
如果您认为fopen
设置了errno
但ifstream::open
不设置,请使用fopen
。但请注意,ISO C 在fopen
部分中没有提及errno
(将其与fgetpos
、fsetpos
、ftell
、...进行比较)。另请注意,ISO C 准确地知道 errno
的 3 个错误代码,而 ISO C++ 提到所有 POSIX 错误代码并将它们映射到 errc
。
我很确定 iostreams 不保证 errno 将在失败时保留。确实,可以看出它很可能会被覆盖。
这段代码也使用 system_category 而不是 generic_category 这是错误的。【参考方案5】:
检查此代码:
uSTL 是 C++ 标准库的部分实现,专注于 减少用户可执行文件的内存占用。
https://github.com/msharov/ustl/blob/master/fstream.cc
【讨论】:
我们为什么要在这个问题的上下文中检查该代码? 那么你应该在论证中说明原因/方式。这是一个仅链接的答案,显示很少或根本没有努力。 (我查看了代码,它当然无助于正确和/或可移植地处理错误情况)。项目负责人A word:“Windows 不受支持,也不会受到支持。我们都应该尽自己的一份力量阻止 Windows 的存在。” windows支持问题无关紧要,我只点fstream实现。 哇。所以,你没有读过这个问题。事实上,甚至不是第一个词。标题。以上是关于在 C++ 中读取文件并处理可能的错误的便携式方法的主要内容,如果未能解决你的问题,请参考以下文章