web-dev-qa-db-fra.com

Manière portable de lire un fichier en C++ et de gérer les erreurs possibles

Je veux faire une chose simple: lire une première ligne d'un fichier et signaler correctement les erreurs au cas où il n'y aurait pas de fichier de ce type, pas de permission de lire le fichier, etc.

J'ai examiné les options suivantes:

  • std::ifstream. Malheureusement, il n’existe aucun moyen portable de signaler les erreurs système. Certaines autres réponses suggèrent de vérifier errno après que la lecture ait échoué, mais la norme ne garantit pas que errno est défini par les fonctions de la bibliothèque iostreams.
  • C style fopen/fread/fclose. Cela fonctionne, mais n’est pas aussi pratique que iostreams avec std::getline. Je cherche une solution C++.

Est-il possible d'accomplir cela en utilisant C++ 14 et boost?

15
Oleg Andriyanov

Disclaimer: Je suis l'auteur de AFIO. Mais exactement ce que vous recherchez, c’est https://ned14.github.io/afio/ qui est la bibliothèque v2 intégrant les commentaires de son évaluation par les pairs Boost d’août 2015. Consultez la liste des fonctionnalités ici .

Je tiens évidemment à préciser qu'il s'agit d'une bibliothèque de qualité alpha et que vous ne devez pas l'utiliser dans le code de production. Cependant, pas mal de gens le font déjà.

Comment utiliser AFIO pour résoudre le problème du PO:

Notez que AFIO est une librairie de très bas niveau, vous devez donc taper beaucoup plus de code pour obtenir le même résultat que iostreams. Par contre, vous n’obtenez aucune allocation de mémoire, aucun jeton d’exception, aucun pic de latence imprévisible:

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

Norme ISO C++:

Le contenu de l'en-tête "cerrno" sont les mêmes que l'en-tête POSIX "errno.h" , excepté errno doit être défini comme une macro. [ Remarque: Le but est de rester en alignement étroit avec le standard POSIX . - fin Remarque ] Un séparé errno la valeur doit être fournie pour chaque fil.

0
Hans

Les personnes figurant sur la liste de diffusion de boost-users ont fait remarquer que la bibliothèque boost.beast possède une API indépendante du système d'exploitation pour le fichier de base IO , y compris le traitement correct des erreurs. Il y a trois implémentations du concept de fichier out-of-the-box: POSIX, stdio et win32. Les implémentations prennent en charge RAII (fermeture automatique lors de la destruction) et la sémantique du déplacement. Le modèle de fichier POSIX gère automatiquement l'erreur EINTR. En gros, cela est suffisant et pratique pour lire de manière portable un morceau de fichier par morceau et, par exemple, gérer explicitement la situation d'absence de fichier:

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

La meilleure chose à faire pourrait être d'encapsuler Boost WinAPI et ou les API POSIX.

La bibliothèque standard "naïve" C++ (avec cloches et sifflets) ne vous mène pas trop loin:

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

}

Imprime ...:

main.cpp: 823 bytes read
nonexistent.cpp: basic_ios::clear: iostream error code iostream:1 (iostream error)
  1. Bien sûr, vous pouvez ajouter plus de diagnostics en parcourant <filesystem> mais cela est sujet à des courses, comme mentionné (en fonction de votre application, ceux-ci peuvent même ouvrir des vulnérabilités de sécurité, donc dites simplement "Non").

  2. Utiliser boost::filesystem::ifstream ne modifie pas les exceptions déclenchées

  3. Pire encore, l'utilisation de Boost IOstream ne génère aucune erreur:

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

    Imprime avec bonheur:

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

    Live On Colir

0
sehe