Портативный способ чтения файла на С++ и обработки возможных ошибок

Я хочу сделать простую вещь: прочитать первую строку из файла и правильно составить отчет об ошибках, если такого файла нет, нет разрешения на чтение файла и т.д.

Я рассмотрел следующие варианты:

  • std::ifstream. К сожалению, нет портативного способа сообщить о системных ошибках. В некоторых других ответах предлагается проверять errno после сбоя чтения, но стандарт не гарантирует, что errno устанавливается какими-либо функциями в библиотеке iostreams.
  • Стиль C fopen/fread/fclose. Это работает, но не так удобно, как iostreams с std::getline. Я ищу решение C++.

Есть ли способ сделать это, используя C++ 14 и повысить?

Ответы

Ответ 1

Отказ от ответственности: я являюсь автором AFIO. Но именно то, что вы ищете, это https://ned14.github.io/afio/, который является библиотекой v2, включающей отзывы от своего экспертного обзора Boost в августе 2015 года. См. список функций здесь.

Конечно, я буду предупреждать, что это альфа-качественная библиотека, и вы не должны использовать ее в производственном коде. Тем не менее, довольно много людей уже делают это.

Как использовать AFIO для решения проблемы OP:

Обратите внимание, что AFIO - это библиотека с очень низким уровнем, поэтому вам нужно ввести намного больше кода для достижения того же самого, что и iostreams, с другой стороны, вы не получаете выделение памяти, не выбрасываете исключение, нет непредсказуемых всплесков задержки:

  // 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);
  }

Ответ 2

Лучше всего было бы обернуть Boost WinAPI и/или API POSIX.

"Наивная" стандартная библиотечная вещь С++ (с колокольчиками и свистами) не заходит слишком далеко:

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

Ответ 3

#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 С++:

Содержимое заголовка "Cerrno" те же, что и заголовок POSIX "Errno.h", Кроме этого ERRNO должен определяется как макрос. [ Заметка: Цель состоит в том, чтобы оставаться в тесном согласии с стандартом POSIX. - конец заметка ] Отдельный ERRNO значение должно быть указано для каждого потока.

Ответ 4

Люди из списка рассылки boost-users отметили, что библиотека boost.beast имеет независимый от ОС API для базового ввода-вывода файлов, включая правильную обработку ошибок. Существует три реализации концепции файлов: 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 {
    // ...
}

Ответ 5

проверьте этот код:

uSTL - это частичная реализация стандартной библиотеки С++, которая фокусируется на уменьшая объем памяти пользовательских исполняемых файлов.

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