以C++读取文件并处理可能的错误的便携式方法

Portable way to read a file in C++ and handle possible errors

本文关键字:错误 便携式 方法 处理 C++ 读取 文件      更新时间:2023-10-16

我想做一件简单的事情:从文件中读取第一行,并在没有此类文件,没有读取文件的权限等情况下进行适当的错误报告。

我考虑了以下选择:

  • std::ifstream.不幸的是,没有便携式方法来报告系统错误。其他一些答案建议在读取失败后检查errno,但该标准并不能保证errno是由iostreams库中的任何函数设置的。
  • C式fopen/fread/fclose。这有效,但不如带有std::getline的iostreams方便。我正在寻找C++解决方案。

有没有办法使用 C++14 和提升来实现这一点?

免责声明:我是AFIO的作者。但您正在寻找的正是 v2 库 https://ned14.github.io/afio/它包含了 2015 年 8 月其 Boost 同行评审的反馈。请参阅此处的功能列表。

我当然要警告的是,这是一个 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);
}

boost-users邮件列表上的人指出,boost.beast库具有独立于操作系统的API用于基本文件IO,包括正确的错误处理。文件概念有三种开箱即用的实现: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 {
// ...
}

最好的办法可能是包装Boost WinAPI和/或POSIX API。

"幼稚"C++标准的图书馆的东西(有铃铛和花铃(不会让你走得太远:

住在科里鲁

#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 readn";
} 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)
  1. 当然,您可以添加更多诊断<filesystem>但是 如前所述,这容易受到种族的影响(根据您的应用程序,这些甚至可以打开安全漏洞,因此只需说"不"(。

  2. 使用boost::filesystem::ifstream不会更改引发的异常

  3. 更糟糕的是,使用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
    

    住在科里鲁

#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 标头相同 "哎呀" ,除了 埃尔诺 将 定义为宏。[ 注意: 目的是与POSIX标准保持密切联系。 — 完 注意 ] 一个单独的 埃尔诺 应为每个线程提供值。

检查以下代码:

uSTL 是 C++ 标准库的部分实现,专注于 减少用户可执行文件的内存占用。

https://github.com/msharov/ustl/blob/master/fstream.cc