web-dev-qa-db-ja.com

C ++でファイルを読み取り、起こりうるエラーを処理するポータブルな方法

私は簡単なことをしたいと思います。ファイルから最初の行を読み取り、そのようなファイルがない場合やファイルを読み取る権限がない場合などに適切なエラー報告を行います。

私は次のオプションを検討しました。

  • std::ifstream。残念ながら、システムエラーを報告するポータブルな方法はありません。他のいくつかの回答は、読み取りに失敗した後にerrnoをチェックすることを提案していますが、標準はerrnoがiostreamsライブラリの関数によって設定されることを保証しません。
  • Cスタイルfopen/fread/fclose。これは機能しますが、std::getlineを使用するiostreamほど便利ではありません。 C++ソリューションを探しています。

C++ 14とブーストを使用してこれを達成する方法はありますか?

16
Oleg Andriyanov

免責事項:私はAFIOの作者です。しかし、まさにあなたが探しているのは https://ned14.github.io/afio/ これは2015年8月のBoostピアレビューからのフィードバックを組み込んだv2ライブラリです。 ここに機能のリスト

もちろん、これはアルファ品質のライブラリであり、本番コードでは使用しないでください。しかし、かなりの数の人々がすでにそうしています。

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);
  }
18
Niall Douglas
#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++標準:

ヘッダー「cerrno」の内容は、errnoがマクロとして定義されることを除いて、POSIXヘッダー「errno.h」と同じです。 [注:目的は、POSIX標準と緊密に連携し続けることです。 —エンドノート]スレッドごとに個別のerrno値を指定する必要があります。

0
Hans

Boost-usersメーリングリストの人々は、 boost.beast ライブラリには基本ファイル用のOSに依存しないAPIがあると指摘しましたIO適切なエラー処理ファイルの概念 の3つの実装があります: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 {
    // ...
}
0
Oleg Andriyanov

最善の方法は、ラップすることです Boost WinAPI またはPOSIXAPI。

「ナイーブな」C++標準ライブラリのもの(ベルとホイッスル付き)は、それほど遠くまでは行きません。

Live On Colir

#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)
  1. もちろん、<filesystem>を熟読する診断を追加することもできますが、前述のように、競合の影響を受けやすくなります(アプリケーションによっては、セキュリティの脆弱性が発生する可能性があるため、「いいえ」と言ってください)。

  2. boost::filesystem::ifstreamを使用しても、発生した例外は変更されません

  3. さらに悪いことに、BoostIOstreamを使用してもエラーは発生しません。

    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 Colir

0
sehe