it-swarm.com.de

Tragbare Möglichkeit, eine Datei in C++ zu lesen und mögliche Fehler zu behandeln

Ich möchte eine einfache Sache tun: Lesen Sie eine erste Zeile aus einer Datei und führen Sie eine ordnungsgemäße Fehlermeldung aus, falls keine solche Datei vorhanden ist, keine Berechtigung zum Lesen der Datei usw.

Ich habe folgende Optionen in Betracht gezogen:

  • std::ifstream. Leider gibt es keine tragbare Möglichkeit, Systemfehler zu melden. Einige andere Antworten schlagen vor, errno zu überprüfen, nachdem das Lesen fehlgeschlagen ist. Der Standard garantiert jedoch nicht, dass errno von den Funktionen der iostreams-Bibliothek festgelegt wird.
  • C style fopen/fread/fclose. Dies funktioniert, ist aber nicht so praktisch wie Iostreams mit std::getline. Ich suche eine C++ - Lösung.

Gibt es eine Möglichkeit, dies mit C++ 14 und Boost zu erreichen?

15
Oleg Andriyanov

Haftungsausschluss: Ich bin der Autor von AFIO. Genau das, wonach Sie suchen, ist https://ned14.github.io/afio/ . Dies ist die v2-Bibliothek, die das Feedback des Boost-Peer-Reviews im August 2015 enthält. Siehe die Liste der Funktionen hier .

Ich möchte natürlich darauf hinweisen, dass dies eine Alpha-Qualitätsbibliothek ist, und Sie sollten sie nicht im Produktionscode verwenden. Allerdings tun das schon einige.

So verwenden Sie AFIO zur Lösung des OP-Problems:

Beachten Sie, dass es sich bei AFIO um eine Bibliothek auf sehr niedrigem Niveau handelt. Daher müssen Sie viel mehr Code eingeben, um das gleiche wie iostreams zu erreichen. Andererseits erhalten Sie keine Speicherzuordnung, keine Ausnahmebedingung, keine unvorhersehbaren Latenzspitzen:

  // 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++ - Standard:

Der Inhalt der Kopfzeile "cerrno" sind die gleichen wie der POSIX-Header "errno.h" , außer dass errno soll als Makro definiert sein. [ Hinweis: Es ist beabsichtigt, in enger Abstimmung mit dem POSIX-Standard zu bleiben . - Ende Hinweis ] Ein separates errno Für jeden Thread ist ein Wert anzugeben.

0
Hans

Die Leute auf der Boost-Benutzer-Mailingliste wiesen darauf hin, dass die Bibliothek boost.beast über eine betriebssystemunabhängige API für die Basisdatei IO verfügt, einschließlich der korrekten Fehlerbehandlung. Es gibt drei Implementierungen des Dateikonzepts out-of-the-box: POSIX, stdio und win32. Die Implementierungen unterstützen RAII (automatisches Schließen bei Zerstörung) und verschieben die Semantik. Das POSIX-Dateimodell behandelt automatisch EINTR-Fehler. Grundsätzlich ist dies ausreichend und bequem, um einen Dateiblock für Abschnitte portabel zu lesen und beispielsweise die Situation des Fehlens einer Datei explizit zu behandeln:

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

Am besten umbrechen Sie Boost WinAPI und/oder POSIX-APIs.

Die "naive" C++ - Standardbibliothekssache (mit Schnickschnack) bringt Sie nicht zu weit:

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");

}

Drucke ...:

main.cpp: 823 bytes read
nonexistent.cpp: basic_ios::clear: iostream error code iostream:1 (iostream error)
  1. Natürlich können Sie weitere Diagnosen hinzufügen, indem Sie <filesystem> lesen, aber wie bereits erwähnt ist dies anfällig für Rennen (abhängig von Ihrer Anwendung können diese sogar Sicherheitslücken aufzeigen, sagen Sie einfach "Nein").

  2. Durch die Verwendung von boost::filesystem::ifstream werden die Ausnahmen nicht geändert

  3. Schlimmer noch, die Verwendung von Boost IOstream löst keine Fehler aus:

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

    Druckt gerne:

    main.cpp: 956 bytes read
    nonexistent.cpp: 0 bytes read
    

    Live On Colir

0
sehe