Como faço para ler um arquivo em um std::string
, ou seja, ler o arquivo inteiro de uma só vez?
O modo texto ou binário deve ser especificado pelo chamador. A solução deve ser compatível com os padrões, portátil e eficiente. Ele não deve copiar desnecessariamente os dados da sequência e evitar realocações de memória durante a leitura da sequência.
Uma maneira de fazer isso seria declarar o tamanho do arquivo, redimensionar o std::string
e fread()
para o std::string
's const_cast<char*>()
' ed data()
. Isso requer que os std::string
dados 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 std::string
tamanho do arquivo pode não ser igual ao tamanho do arquivo.
Soluções totalmente corretas, compatíveis com os padrões e portáteis podem ser construídas usando std::ifstream
's rdbuf()
em ae std::ostringstream
de lá em a std::string
. No entanto, isso pode copiar os dados da string e / ou realocar desnecessariamente a memória.
- 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?
- Perdi alguma função Boost oculta que já fornece a funcionalidade desejada?
void slurp(std::string& data, bool is_binary)
rdbuf
(aquele na resposta aceita) não é o mais rápido,read
é.Respostas:
Uma maneira é liberar o buffer de fluxo em um fluxo de memória separado e depois convertê-lo para
std::string
:Isso é bem conciso. No entanto, conforme observado na pergunta, isso executa uma cópia redundante e, infelizmente, não existe maneira fundamental de excluir essa cópia.
A única solução real que evita cópias redundantes é fazer a leitura manualmente em um loop, infelizmente. Como o C ++ agora tem seqüências de caracteres contíguas garantidas, pode-se escrever o seguinte (≥C ++ 14):
fonte
string
. Ou seja, exigindo o dobro de memória que algumas das outras opções. (Não há como mover o buffer). Para um arquivo grande, isso seria uma penalidade significativa, talvez até causando uma falha na alocação.Veja esta resposta em uma pergunta semelhante.
Para sua comodidade, estou reposicionando a solução dos CTT:
Essa solução resultou em tempos de execução cerca de 20% mais rápidos do que as outras respostas apresentadas aqui, ao fazer a média de 100 execuções contra o texto de Moby Dick (1,3M). Nada mal para uma solução portátil C ++, eu gostaria de ver os resultados de mmap'ing do arquivo;)
fonte
ifs.seekg(0, ios::end)
antestellg
? apenas depois de abrir um arquivo de leitura ponteiro está no início e assimtellg
retorna zeronullptr
por&bytes[0]
ios::ate
, então eu acho que uma versão com movimento explícita à final seria mais legívelA variante mais curta: Live On Coliru
Requer o cabeçalho
<iterator>
.Houve alguns relatos de que esse método é mais lento do que pré-alocar a string e usá-la
std::istream::read
. No entanto, em um compilador moderno com otimizações ativadas, isso não parece mais ser o caso, embora o desempenho relativo de vários métodos pareça ser altamente dependente do compilador.fonte
Usar
ou algo muito próximo. Não tenho uma referência stdlib aberta para me verificar novamente.
Sim, entendo que não escrevi a
slurp
função conforme solicitado.fonte
operator>>
lê em astd::basic_streambuf
, ele consome (o que resta) do fluxo de entrada, portanto o loop é desnecessário.Se você possui C ++ 17 (std :: filesystem), também existe este caminho (que obtém o tamanho do arquivo em
std::filesystem::file_size
vez deseekg
etellg
):Nota : pode ser necessário usá-lo
<experimental/filesystem>
estd::experimental::filesystem
se sua biblioteca padrão ainda não suportar totalmente o C ++ 17. Você também pode precisar substituíresult.data()
-&result[0]
lo por se ele não suportar dados não-const std :: basic_string .fonte
boost::filesystem
para que você também possa usar o boost se não tiver c ++ 17Eu não tenho reputação suficiente para comentar diretamente sobre as respostas usando
tellg()
.Esteja ciente de que
tellg()
pode retornar -1 em caso de erro. Se você estiver passando o resultadotellg()
como um parâmetro de alocação, verifique primeiro o sanidade.Um exemplo do problema:
No exemplo acima, se
tellg()
encontrar um erro, ele retornará -1. A conversão implícita entre assinado (ou seja, o resultado detellg()
) e não assinado (ou seja, o argumento para ovector<char>
construtor) resultará em um vetor que aloca erroneamente um número muito grande de bytes. (Provavelmente 4294967295 bytes ou 4 GB.)Modificando a resposta de paxos1977 para explicar o que foi dito acima:
fonte
Esta solução adiciona a verificação de erros ao método baseado em rdbuf ().
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 stringstream (
str_stream << file_stream.rdbuf()
). O problema é que isso define o failbit do stringstream quando nenhum caractere é inserido. Isso pode ocorrer devido a um erro ou ao arquivo estar vazio. Se você verificar falhas ao inspecionar o failbit, encontrará um falso positivo ao ler um arquivo vazio. Como você desambigua a falha legítima em inserir caracteres e a "falha" em inserir caracteres porque o arquivo está vazio?Você pode procurar explicitamente um arquivo vazio, mas isso significa 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 falha
file_stream.fail() && !file_stream.eof()
.É importante
file_stream >> str_stream.rdbuf()
ressaltar que , quando encontra uma falha legítima, ele nunca deve definir o eofbit (de acordo com meu entendimento da especificação). Isso significa que a verificação acima é suficiente para detectar falhas legítimas.fonte
Algo assim não deve ser tão ruim:
A vantagem aqui é que fazemos a reserva primeiro, para que não tenhamos que crescer a string enquanto lemos as coisas. A desvantagem é que fazemos char a char. Uma versão mais inteligente pode pegar todo o buf de leitura e, em seguida, chamar underflow.
fonte
Aqui está uma versão usando a nova biblioteca de sistemas de arquivos com verificação de erro razoavelmente robusta:
fonte
infile.open
também pode aceitarstd::string
sem converter com.c_str()
filepath
não é umstd::string
, é umstd::filesystem::path
. Acontece questd::ifstream::open
pode aceitar um desses também.std::filesystem::path
é implicitamente conversível emstd::string
::open
função de membrostd::ifstream
que aceitastd::filesystem::path
opera como se o::c_str()
método fosse chamado no caminho. O subjacente::value_type
dos caminhos estáchar
no POSIX.Você pode usar a função 'std :: getline' e especificar 'eof' como delimitador. O código resultante é um pouco obscuro:
fonte
Nunca escreva no buffer const char * do std :: string. Jamais! Fazer isso é um erro enorme.
Reserve () espaço para toda a cadeia de caracteres em seu std :: string, leia trechos do seu arquivo de tamanho razoável em um buffer e inclua (). O tamanho dos pedaços depende do tamanho do arquivo de entrada. Tenho certeza de que todos os outros mecanismos portáteis e compatíveis com STL farão o mesmo (mas podem parecer mais bonitos).
fonte
std::string
buffer; e acredito que funcionou corretamente em todas as implementações reais anteriores a isso #std::string::data()
método não-const para modificar o buffer de strings diretamente, sem recorrer a truques como&str[0]
.uso:
fonte
Uma função atualizada que se baseia na solução dos CTT:
Existem duas diferenças importantes:
tellg()
Não é garantido o retorno do deslocamento em bytes desde o início do arquivo. Em vez disso, como Puzomor Croatia apontou, é mais um token que pode ser usado nas chamadas fstream.gcount()
no entanto , retorna a quantidade de bytes não formatados extraídos pela última vez. Portanto, abrimos o arquivo, extraímos e descartamos todo o seu conteúdoignore()
para obter o tamanho do arquivo e construímos a string de saída com base nisso.Em segundo lugar, evitamos ter que copiar os dados do arquivo de a
std::vector<char>
para astd::string
escrevendo diretamente na string.Em termos de desempenho, esse deve ser o mais rápido possível, alocando a sequência de tamanho apropriada com antecedência e ligando
read()
uma vez. Como um fato interessante, usarignore()
e emcountg()
vez deate
etellg()
no gcc compila quase a mesma coisa , pouco a pouco.fonte
ifs.seekg(0)
vez deifs.clear()
(então funciona).fonte