274 votes

Comment lire un fichier entier dans une chaîne std::string en C++ ?

Comment lire un fichier dans un std::string c'est-à-dire lire tout le fichier en une seule fois ?

Le mode texte ou binaire doit être spécifié par l'appelant. La solution doit être conforme aux normes, portable et efficace. Elle ne doit pas copier inutilement les données de la chaîne et doit éviter les réaffectations de mémoire lors de la lecture de la chaîne.

Une façon d'y parvenir serait de statuer sur la taille des fichiers, de redimensionner les std::string y fread() dans le std::string 's const_cast<char*>() 'ed data() . Cela nécessite le std::string Les données de l'utilisateur sont contiguës, ce qui n'est pas requis par la norme, mais semble être le cas pour toutes les implémentations connues. Pire encore, si le fichier est lu en mode texte, la fonction std::string La taille du fichier peut ne pas être égale à la taille du fichier.

Une solution tout à fait correcte, conforme aux normes et portable pourrait être construite en utilisant std::ifstream 's rdbuf() en un std::ostringstream et de là, dans un std::string . Cependant, cela pourrait copier les données de la chaîne et/ou réallouer inutilement de la mémoire.

  • Toutes les implémentations pertinentes de la bibliothèque standard sont-elles suffisamment intelligentes pour éviter toute surcharge inutile ?
  • Y a-t-il un autre moyen de le faire ?
  • Ai-je manqué une fonction cachée de Boost qui fournit déjà la fonctionnalité souhaitée ?

    void slurp(std::string& data, bool is_binary)

20voto

Rick Ramstetter Points 312

Je n'ai pas assez de réputation pour commenter directement les réponses utilisant tellg() .

Veuillez noter que tellg() peut retourner -1 en cas d'erreur. Si vous passez le résultat de tellg() en tant que paramètre d'allocation, vous devez d'abord vérifier l'intégrité du résultat.

Un exemple du problème :

...
std::streamsize size = file.tellg();
std::vector<char> buffer(size);
...

Dans l'exemple ci-dessus, si tellg() rencontre une erreur, il renvoie -1. Le moulage implicite entre signés (c'est-à-dire le résultat de tellg() ) et non signée (c'est-à-dire l'argument de la fonction vector<char> ) aura pour conséquence que votre vecteur allouera par erreur une valeur de très un grand nombre d'octets. (Probablement 4294967295 octets, ou 4GB.)

Modification de la réponse de paxos1977 pour tenir compte de ce qui précède :

string readFile2(const string &fileName)
{
    ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate);

    ifstream::pos_type fileSize = ifs.tellg();
    if (fileSize < 0)                             <--- ADDED
        return std::string();                     <--- ADDED

    ifs.seekg(0, ios::beg);

    vector<char> bytes(fileSize);
    ifs.read(&bytes[0], fileSize);

    return string(&bytes[0], fileSize);
}

11voto

b.g. Points 101

Puisque cela semble être un utilitaire largement utilisé, mon approche serait de rechercher et de préférer les bibliothèques déjà disponibles aux solutions faites à la main, surtout si les bibliothèques boost sont déjà liées (linker flags -lboost_system -lboost_filesystem) dans votre projet. Ici (et les anciennes versions de boost aussi) boost fournit un utilitaire load_string_file :

#include <iostream>
#include <string>
#include <boost/filesystem/string_file.hpp>

int main() {
    std::string result;
    boost::filesystem::load_string_file("aFileName.xyz", result);
    std::cout << result.size() << std::endl;
}

Comme avantage, cette fonction ne cherche pas un fichier entier pour déterminer la taille, mais utilise stat() en interne. Un inconvénient peut-être négligeable est que l'on peut facilement déduire de l'inspection du code source que la chaîne de caractères est inutilement redimensionnée avec la fonction '\0' qui sont réécrits par le contenu du fichier.

9voto

tgnottingham Points 308

Cette solution ajoute un contrôle d'erreur à la méthode basée sur rdbuf()-.

std::string file_to_string(const std::string& file_name)
{
    std::ifstream file_stream{file_name};

    if (file_stream.fail())
    {
        // Error opening file.
    }

    std::ostringstream str_stream{};
    file_stream >> str_stream.rdbuf();  // NOT str_stream << file_stream.rdbuf()

    if (file_stream.fail() && !file_stream.eof())
    {
        // Error reading file.
    }

    return str_stream.str();
}

J'ajoute cette réponse parce que l'ajout de la vérification des erreurs à la méthode originale n'est pas aussi trivial qu'on pourrait le croire. La méthode originale utilise l'opérateur d'insertion de stringstream ( str_stream << file_stream.rdbuf() ). Le problème est que cela active le failbit du stringstream lorsqu'aucun caractère n'est inséré. Cela peut être dû à une erreur ou au fait que le fichier est vide. Si vous vérifiez les échecs en inspectant le failbit, vous rencontrerez un faux positif lorsque vous lirez un fichier vide. Comment distinguer l'échec légitime de l'insertion de caractères de l'"échec" de l'insertion de caractères parce que le fichier est vide ?

Vous pourriez penser à vérifier explicitement l'existence d'un fichier vide, mais cela implique davantage de code et de vérifications d'erreurs associées.

Vérification de la condition d'échec str_stream.fail() && !str_stream.eof() ne fonctionne pas, car l'opération d'insertion ne met pas en place le eofbit (sur le ostringstream ou le ifstream).

La solution consiste donc à modifier l'opération. Au lieu d'utiliser l'opérateur d'insertion d'ostringstream (<<), utiliser l'opérateur d'extraction d'ifstream (>>), qui définit le eofbit. Ensuite, vérifiez la condition d'échec file_stream.fail() && !file_stream.eof() .

Il est important de noter que lorsque file_stream >> str_stream.rdbuf() rencontre un échec légitime, il ne devrait jamais activer eofbit (selon ma compréhension des spécifications). Cela signifie que la vérification ci-dessus est suffisante pour détecter les échecs légitimes.

6voto

Matt Price Points 9674

Quelque chose comme ça ne devrait pas être trop grave :

void slurp(std::string& data, const std::string& filename, bool is_binary)
{
    std::ios_base::openmode openmode = ios::ate | ios::in;
    if (is_binary)
        openmode |= ios::binary;
    ifstream file(filename.c_str(), openmode);
    data.clear();
    data.reserve(file.tellg());
    file.seekg(0, ios::beg);
    data.append(istreambuf_iterator<char>(file.rdbuf()), 
                istreambuf_iterator<char>());
}

L'avantage est que nous faisons d'abord la réserve pour ne pas avoir à agrandir la chaîne au fur et à mesure que nous lisons les données. L'inconvénient est que nous le faisons caractère par caractère. Une version plus intelligente pourrait prendre la totalité du buf lu et ensuite appeler underflow.

6voto

David G Points 430

Voici une version utilisant la nouvelle bibliothèque de système de fichiers avec un contrôle d'erreur raisonnablement robuste :

#include <cstdint>
#include <exception>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>

namespace fs = std::filesystem;

std::string loadFile(const char *const name);
std::string loadFile(const std::string &name);

std::string loadFile(const char *const name) {
  fs::path filepath(fs::absolute(fs::path(name)));

  std::uintmax_t fsize;

  if (fs::exists(filepath)) {
    fsize = fs::file_size(filepath);
  } else {
    throw(std::invalid_argument("File not found: " + filepath.string()));
  }

  std::ifstream infile;
  infile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
  try {
    infile.open(filepath.c_str(), std::ios::in | std::ifstream::binary);
  } catch (...) {
    std::throw_with_nested(std::runtime_error("Can't open input file " + filepath.string()));
  }

  std::string fileStr;

  try {
    fileStr.resize(fsize);
  } catch (...) {
    std::stringstream err;
    err << "Can't resize to " << fsize << " bytes";
    std::throw_with_nested(std::runtime_error(err.str()));
  }

  infile.read(fileStr.data(), fsize);
  infile.close();

  return fileStr;
}

std::string loadFile(const std::string &name) { return loadFile(name.c_str()); };

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X