Qual é a melhor maneira de ler um arquivo inteiro em um std :: string em C ++?

Como faço para ler um arquivo em um std::string , ou seja, ler o arquivo inteiro de uma só vez?

O modo de texto ou binário deve ser especificado pelo chamador. A solução deve ser compatível com o padrão, portátil e eficiente. Ele não deve copiar desnecessariamente os dados da seqüência de caracteres e deve evitar realocações de memory durante a leitura da seqüência de caracteres.

Uma maneira de fazer isso seria fread() o tamanho do arquivo, resize o std::string e fread() em const_cast() ‘ed data() do std::string . Isso requer que os dados do std::string sejam contíguos, o que não é exigido pelo padrão, mas parece ser o caso de todas as implementações conhecidas. O que é pior, se o arquivo for lido no modo de texto, o tamanho do std::string pode não ser igual ao tamanho do arquivo.

Uma solução totalmente correta, compatível com o padrão e portável poderia ser construída usando o rdbuf() std::ifstream em um std::ostringstream e de lá para um std::string . No entanto, isso pode copiar os dados da cadeia e / ou realocar desnecessariamente a memory. Todas as implementações relevantes de bibliotecas padrão são inteligentes o suficiente para evitar toda a sobrecarga desnecessária? tem outro jeito de fazer isto? Eu perdi alguma function Boost escondida que já fornece a funcionalidade desejada?

Por favor, mostre sua sugestão de como implementá-lo.

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

levando em conta a discussão acima.

E o mais rápido (que eu conheço, descontando arquivos mapeados na memory):

 std::string str(static_cast(std::stringstream() << in.rdbuf()).str()); 

Isso requer o header adicional para o stream de strings. (O static_cast é necessário, pois o operator << retorna um ostream& antigo ostream& simples, mas sabemos que, na realidade, é um stringstream& portanto, o casting é seguro.)

Dividido em várias linhas, movendo o temporário para uma variável, obtemos um código mais legível:

 std::string slurp(std::ifstream& in) { std::stringstream sstr; sstr << in.rdbuf(); return sstr.str(); } 

Ou, mais uma vez, em uma única linha:

 std::string slurp(std::ifstream& in) { return static_cast(std::stringstream() << in.rdbuf()).str(); } 

Veja esta resposta em uma pergunta semelhante.

Para sua conveniência, estou repassando a solução da CTT:

 string readFile2(const string &fileName) { ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate); ifstream::pos_type fileSize = ifs.tellg(); ifs.seekg(0, ios::beg); vector bytes(fileSize); ifs.read(bytes.data(), fileSize); return string(bytes.data(), fileSize); } 

Esta solução resultou em cerca de 20% de tempos de execução mais rápidos do que as outras respostas aqui apresentadas, ao considerar a média de 100 execuções em relação ao texto de Moby Dick (1.3M). Nada mal para uma solução C ++ portátil, gostaria de ver os resultados do mmap’ing do arquivo;)

A variante mais curta: Live On Coliru

 std::string str(std::istreambuf_iterator{ifs}, {}); 

Requer o header .

Houve alguns relatos de que esse método é mais lento do que pré-alocar a string e usar std::istream::read . No entanto, em um compilador moderno com otimizações habilitadas, isso não parece mais ser o caso, embora o desempenho relativo de vários methods pareça ser altamente dependente do compilador.

Usar

 #include  #include  #include  int main() { std::ifstream input("file.txt"); std::stringstream sstr; while(input >> sstr.rdbuf()); std::cout << sstr.str() << std::endl; } 

ou algo muito próximo. Eu não tenho uma referência stdlib aberta para me checar novamente.

Sim, entendo que não escrevi a function slurp como solicitado.

Eu não tenho reputação suficiente para comentar diretamente as respostas usando o tellg() .

Por favor, esteja ciente de que o tellg() pode retornar -1 no erro. Se você está passando o resultado de tellg() como um parâmetro de alocação, você deve verificar o resultado primeiro.

Um exemplo do problema:

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

No exemplo acima, se tellg() encontrar um erro, ele retornará -1. A conversão implícita entre assinado (isto é, o resultado de tellg() ) e não assinado (isto é, o arg para o vector construtor) resultará em um vetor erroneamente alocando um número muito grande de bytes. (Provavelmente 4294967295 bytes ou 4 GB.)

Modificando a resposta de paxos1977 para explicar o acima:

 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 bytes(fileSize); ifs.read(&bytes[0], fileSize); return string(&bytes[0], fileSize); } 

Nunca escreva no buffer const char * da std :: string. Jamais! Isso é um grande erro.

Reserve () espaço para a string inteira em seu std :: string, leia trechos do seu arquivo de tamanho razoável em um buffer, e anexe-o (). O tamanho dos blocos depende do tamanho do arquivo de input. Tenho certeza de que todos os outros mecanismos portáteis e compatíveis com STL farão o mesmo (mas podem parecer mais bonitos).

Algo como isso não deveria ser tão ruim:

 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(file.rdbuf()), istreambuf_iterator()); } 

A vantagem aqui é que nós fazemos a reserva primeiro, então não teremos que aumentar a string enquanto lemos as coisas. A desvantagem é que fazemos char por char. Uma versão mais inteligente poderia pegar todo o buf de leitura e chamar o underflow.

Você pode usar a function ‘std :: getline’ e especificar ‘eof’ como o delimitador. O código resultante é um pouco obscuro:

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

Se você tem o C ++ 17 (std :: filesystem), existe também este caminho (que obtém o tamanho do arquivo através do std::filesystem::file_size invés de seekg e tellg ):

 #include  #include  #include  namespace fs = std::filesystem; std::string readFile(fs::path path) { // Open the stream to 'lock' the file. std::ifstream f{ path }; // Obtain the size of the file. const auto sz = fs::file_size(path); // Create a buffer. std::string result(sz, ' '); // Read the whole file into the buffer. f.read(result.data(), sz); return result; } 

Nota : você pode precisar usar e std::experimental::filesystem se sua biblioteca padrão ainda não suportar completamente o C ++ 17. Você também pode precisar replace result.data() por &result[0] se ele não suportar dados non-const std :: basic_string .

Essa solução adiciona verificação de erros ao método baseado em 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(); } 

Estou adicionando esta resposta porque adicionar verificação de erros ao método original não é tão trivial quanto você esperaria. O método original usa o operador de inserção do str_stream << file_stream.rdbuf() ( str_stream << file_stream.rdbuf() ). O problema é que isso define o failbit do stringstream quando nenhum caractere é inserido. Isso pode ser devido a um erro ou pode ser devido ao arquivo estar vazio. Se você verificar falhas ao inspecionar o failbit, encontrará um falso positivo ao ler um arquivo vazio. Como você desambigua falha legítima para inserir qualquer caractere e "falha" para inserir qualquer caractere porque o arquivo está vazio?

Você pode pensar em verificar explicitamente um arquivo vazio, mas isso é mais código e verificação de erro associada.

A verificação da condição de falha str_stream.fail() && !str_stream.eof() não funciona, porque a operação de inserção não define o eofbit (no ostringstream nem no ifstream).

Então, a solução é mudar a operação. Em vez de usar o operador de inserção do ostringstream (<<), use o operador de extração do ifstream (>>), que define o eofbit. Em seguida, verifique a condição de file_stream.fail() && !file_stream.eof() .

É importante file_stream >> str_stream.rdbuf() quando file_stream >> str_stream.rdbuf() encontra uma falha legítima, ele nunca deve definir o eofbit (de acordo com o meu entendimento da especificação). Isso significa que a verificação acima é suficiente para detectar falhas legítimas.

E se você está sugando um arquivo de 11K, então você tem que fazê-lo em uma série de pedaços, então você tem que usar algo como std :: vector para fazer slurp em grandes pedaços de strings.