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

Ole*_*nov 15 c++ file-io boost

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

我考虑了以下选项:

  • std::ifstream.不幸的是,没有可移植的方式来报告系统错误.其他一些答案建议errno在读取失败后进行检查,但标准并不保证errno由iostreams库中的任何函数设置.
  • 空调风格fopen/ fread/ fclose.这有效,但不像iostreams那样方便std::getline.我正在寻找C++解决方案.

有没有办法用C++ 14和boost来实现这个目的?

Nia*_*las 18

免责声明:我是AFIO的作者.但正是您正在寻找的是https://ned14.github.io/afio/这是v2库,其中包含了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);
  }
Run Code Online (Sandbox Code Playgroud)

  • @OlegAndriyanov我希望在2019年,我挡在了协程TS返回用afio其第二升压同行评审,范围TS,预计和跨度的建议和未来在C++ 20无需几个其他位添加流缓冲支持,用afio已使用Ranges TS.所以用Ranges说i/o,一切都很好.埃里克做了这个博客帖子在http://ericniebler.com/2017/08/17/ranges-coroutines-and-react-early-musings-on-the-future-of-async-in-c/,他使用libuv代替AFIO,因为它更广为人知.但是AFIO比libuv效率更高*WG21目前正在AFIO上为iostreams v2微笑 (3认同)

seh*_*ehe 0

最好的办法可能是包装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");

}
Run Code Online (Sandbox Code Playgroud)

印刷...:

main.cpp: 823 bytes read
nonexistent.cpp: basic_ios::clear: iostream error code iostream:1 (iostream error)
Run Code Online (Sandbox Code Playgroud)
  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);
    }
    
    Run Code Online (Sandbox Code Playgroud)

    快乐打印:

    main.cpp: 956 bytes read
    nonexistent.cpp: 0 bytes read
    
    Run Code Online (Sandbox Code Playgroud)

    Live On Coliru

  • 信息就是一切。如果您知道,您可以在问题中表明这一点。另外,[我分享咆哮](https://chat.stackoverflow.com/transcript/message/38808019#38808019),但这不是一个有用的答案。(这甚至是一种值得怀疑的态度)。 (3认同)