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)

3voto

Roflcopter4 Points 564

Je sais que c'est une question très ancienne avec une pléthore de réponses, mais aucune d'entre elles ne mentionne ce que j'aurais considéré comme le moyen le plus évident de le faire. Oui, je sais que c'est du C++, et que l'utilisation de la libc est maléfique et incorrecte ou autre, mais c'est une erreur. Utiliser libc est bien, surtout pour une chose aussi simple que celle-ci.

En gros, il suffit d'ouvrir le fichier, d'obtenir sa taille (pas nécessairement dans cet ordre) et de le lire.

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/stat.h>

static constexpr char const filename[] = "foo.bar";

int main(void)
{
    FILE *fp = ::fopen(filename, "rb");
    if (!fp) {
        ::perror("fopen");
        ::exit(1);
    }

    struct stat st;
    if (::fstat(fileno(fp), &st) == (-1)) {
        ::perror("fstat");
        ::exit(1);
    }

    // You could simply allocate a buffer here and use std::string_view, or
    // even allocate a buffer and copy it to a std::string. Creating a
    // std::string and setting its size is simplest, but will pointlessly
    // initialize the buffer to 0. You can't win sometimes.
    std::string str;
    str.reserve(st.st_size + 1U);
    str.resize(st.st_size);
    ::fread(str.data(), 1, st.st_size, fp);
    str[st.st_size] = '\0';
    ::fclose(fp);
}

Cela ne semble pas vraiment pire que certaines des autres solutions, en plus d'être (en pratique) complètement portable. On pourrait aussi lancer une exception au lieu de sortir immédiatement, bien sûr. Cela m'irrite sérieusement que le redimensionnement de la fenêtre std::string toujours 0 l'initialise, mais on ne peut rien y faire.

VEUILLEZ NOTER que cela ne fonctionnera comme écrit que pour C++17 et plus. Les versions antérieures ne permettent pas (devraient permettre) d'éditer std::string::data() . Si vous travaillez avec une version antérieure, pensez à utiliser std::string_view ou simplement en copiant un tampon brut.

2voto

Martin Cote Points 12762

Vous pouvez utiliser la fonction 'std::getline', et spécifier 'eof' comme délimiteur. Le code résultant est cependant un peu obscur :

std::string data;
std::ifstream in( "test.txt" );
std::getline( in, data, std::string::traits_type::to_char_type( 
                  std::string::traits_type::eof() ) );

1voto

Xavier Points 2188

Pour les performances, je n'ai rien trouvé de plus rapide que le code ci-dessous.

std::string readAllText(std::string const &path)
{
    assert(path.c_str() != NULL);
    FILE *stream = fopen(path.c_str(), "r");
    assert(stream != NULL);
    fseek(stream, 0, SEEK_END);
    long stream_size = ftell(stream);
    fseek(stream, 0, SEEK_SET);
    void *buffer = malloc(stream_size);
    fread(buffer, stream_size, 1, stream);
    assert(ferror(stream) == 0);
    fclose(stream);
    std::string text((const char *)buffer, stream_size);
    assert(buffer != NULL);
    free((void *)buffer);
    return text;
}

0voto

Gavriel Feria Points 36

Et si vous aspirez un fichier de 11K, alors vous devez le faire en une série de morceaux, donc vous devez utiliser quelque chose comme std::vector pour l'aspirer en gros morceaux de chaînes.

0voto

Paul Sumpner Points 81
#include <string>
#include <sstream>

using namespace std;

string GetStreamAsString(const istream& in)
{
    stringstream out;
    out << in.rdbuf();
    return out.str();
}

string GetFileAsString(static string& filePath)
{
    ifstream stream;
    try
    {
        // Set to throw on failure
        stream.exceptions(fstream::failbit | fstream::badbit);
        stream.open(filePath);
    }
    catch (system_error& error)
    {
        cerr << "Failed to open '" << filePath << "'\n" << error.code().message() << endl;
        return "Open fail";
    }

    return GetStreamAsString(stream);
}

l'usage :

const string logAsString = GetFileAsString(logFilePath);

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