Портативный способ чтения файла на С++ и обработки возможных ошибок
Я хочу сделать простую вещь: прочитать первую строку из файла и правильно составить отчет об ошибках, если такого файла нет, нет разрешения на чтение файла и т.д.
Я рассмотрел следующие варианты:
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