¿Cómo leo un archivo en un std::string
es decir, leer todo el archivo a la vez?
La persona que llama debe especificar el modo de texto o binario. La solución debe ser compatible con los estándares, portátil y eficiente. No debe copiar innecesariamente los datos de la cadena y debe evitar reasignaciones de memoria mientras lee la cadena.
Una forma de hacer esto sería indicar el tamaño del archivo, cambiar el tamaño del std::string
y fread()
en el std::string
‘s const_cast<char*>()
ed data()
. Esto requiere la std::string
Los datos de sean contiguos, lo que no es requerido por el estándar, pero parece ser el caso para todas las implementaciones conocidas. Lo que es peor, si el archivo se lee en modo texto, el std::string
El tamaño de ‘s puede no ser igual al tamaño del archivo.
Se podrían construir soluciones completamente correctas, compatibles con los estándares y portátiles utilizando std::ifstream
‘s rdbuf()
en un std::ostringstream
y de ahí a un std::string
. Sin embargo, esto podría copiar los datos de la cadena y/o reasignar memoria innecesariamente.
- ¿Todas las implementaciones de bibliotecas estándar relevantes son lo suficientemente inteligentes como para evitar todos los gastos generales innecesarios?
- ¿Hay otra forma de hacerlo?
- ¿Me perdí alguna función Boost oculta que ya proporciona la funcionalidad deseada?
void slurp(std::string& data, bool is_binary)

Konrad Rodolfo
Una forma es vaciar el búfer de flujo en un flujo de memoria separado y luego convertirlo en std::string
(se omite el manejo de errores):
std::string slurp(std::ifstream& in) {
std::ostringstream sstr;
sstr << in.rdbuf();
return sstr.str();
}
Esto es muy conciso. Sin embargo, como se indica en la pregunta, esto realiza una copia redundante y, lamentablemente, fundamentalmente no hay forma de elidir esta copia.
Desafortunadamente, la única solución real que evita las copias redundantes es hacer la lectura manualmente en bucle. Dado que C++ ahora tiene cadenas contiguas garantizadas, se podría escribir lo siguiente (≥C++17, manejo de errores incluido):
auto read_file(std::string_view path) -> std::string {
constexpr auto read_size = std::size_t(4096);
auto stream = std::ifstream(path.data());
stream.exceptions(std::ios_base::badbit);
auto out = std::string();
auto buf = std::string(read_size, '\0');
while (stream.read(& buf[0], read_size)) {
out.append(buf, 0, stream.gcount());
}
out.append(buf, 0, stream.gcount());
return out;
}

Konrad Rodolfo
La variante más corta: Vive en Coliru
std::string str(std::istreambuf_iterator<char>{ifs}, {});
Requiere el encabezado <iterator>
.
Hubo algunos informes de que este método es más lento que preasignar la cadena y usar std::istream::read
. Sin embargo, en un compilador moderno con optimizaciones habilitadas, este ya no parece ser el caso, aunque el rendimiento relativo de varios métodos parece depender en gran medida del compilador.

paxos1977
Ver esta respuesta en una pregunta similar.
Para su comodidad, vuelvo a publicar la solución de 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<char> bytes(fileSize);
ifs.read(bytes.data(), fileSize);
return string(bytes.data(), fileSize);
}
Esta solución resultó en tiempos de ejecución un 20 % más rápidos que las otras respuestas presentadas aquí, al tomar el promedio de 100 ejecuciones contra el texto de Moby Dick (1.3M). No está mal para una solución portátil de C++, me gustaría ver los resultados de hacer mmap del archivo;)

Gabriel Majeri
Si tiene C ++ 17 (std::filesystem), también existe esta forma (que obtiene el tamaño del archivo a través de std::filesystem::file_size
en lugar de seekg
y tellg
):
#include <filesystem>
#include <fstream>
#include <string>
namespace fs = std::filesystem;
std::string readFile(fs::path path)
{
// Open the stream to 'lock' the file.
std::ifstream f(path, std::ios::in | std::ios::binary);
// Obtain the size of the file.
const auto sz = fs::file_size(path);
// Create a buffer.
std::string result(sz, '\0');
// Read the whole file into the buffer.
f.read(result.data(), sz);
return result;
}
Nota: es posible que necesite usar <experimental/filesystem>
y std::experimental::filesystem
si su biblioteca estándar aún no es completamente compatible con C++17. Es posible que también deba reemplazar result.data()
con &result[0]
si no es compatible datos no const std::basic_string.

ben collins
Utilizar
#include <iostream>
#include <sstream>
#include <fstream>
int main()
{
std::ifstream input("file.txt");
std::stringstream sstr;
while(input >> sstr.rdbuf());
std::cout << sstr.str() << std::endl;
}
o algo muy cercano. No tengo una referencia de stdlib abierta para verificarme dos veces.
Sí, entiendo que no escribí el slurp
función como se le pide.
No tengo suficiente reputación para comentar directamente las respuestas usando tellg()
.
Tenga en cuenta que tellg()
puede devolver -1 en caso de error. Si estás pasando el resultado de tellg()
como parámetro de asignación, primero debe verificar la cordura del resultado.
Un ejemplo del problema:
...
std::streamsize size = file.tellg();
std::vector<char> buffer(size);
...
En el ejemplo anterior, si tellg()
encuentra un error, devolverá -1. Casting implícito entre firmado (es decir, el resultado de tellg()
) y sin firmar (es decir, el argumento del vector<char>
constructor) dará como resultado que su vector asigne erróneamente un muy gran cantidad de bytes. (Probablemente 4294967295 bytes o 4 GB).
Modificando la respuesta de paxos1977 para tener en cuenta lo anterior:
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);
}

tgnottingham
Esta solución agrega la verificación de errores al método basado en 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();
}
Estoy agregando esta respuesta porque agregar la verificación de errores al método original no es tan trivial como cabría esperar. El método original usa el operador de inserción de stringstream (str_stream << file_stream.rdbuf()
). El problema es que esto establece el bit de error de stringstream cuando no se insertan caracteres. Eso puede deberse a un error o puede deberse a que el archivo está vacío. Si verifica si hay fallas al inspeccionar el bit de falla, encontrará un falso positivo cuando lea un archivo vacío. ¿Cómo desambigua la falla legítima al insertar caracteres y la “falla” al insertar caracteres porque el archivo está vacío?
Puede pensar en buscar explícitamente un archivo vacío, pero eso es más código y verificación de errores asociada.
Comprobación de la condición de falla str_stream.fail() && !str_stream.eof()
no funciona, porque la operación de inserción no establece el eofbit (en ostringstream ni en ifstream).
Entonces, la solución es cambiar la operación. En lugar de usar el operador de inserción de ostringstream (<<), use el operador de extracción de ifstream (>>), que establece el eofbit. Luego verifique la condición de falla file_stream.fail() && !file_stream.eof()
.
Importante, cuando file_stream >> str_stream.rdbuf()
encuentra una falla legítima, nunca debería establecer eofbit (según mi comprensión de la especificación). Eso significa que la verificación anterior es suficiente para detectar fallas legítimas.
Tenga en cuenta que todavía tiene algunas cosas subespecificadas. Por ejemplo, ¿cuál es la codificación de caracteres del archivo? ¿Intentará la detección automática (que solo funciona en algunos casos específicos)? ¿Respetará, por ejemplo, los encabezados XML que le indican la codificación del archivo? Además, no existe el “modo de texto” o el “modo binario”. ¿Estás pensando en FTP?
–Jason Cohen
22 de septiembre de 2008 a las 16:51
El modo de texto y binario son trucos específicos de MSDOS y Windows que intentan evitar el hecho de que las nuevas líneas están representadas por dos caracteres en Windows (CR/LF). En el modo de texto, se tratan como un carácter (‘\n’).
– Ferruccio
22 de septiembre de 2008 a las 16:54
Aunque no es (bastante) un duplicado exacto, esto está estrechamente relacionado con: ¿cómo preasignar memoria para un objeto std::string? (que, contrariamente a la declaración anterior de Konrad, incluía un código para hacer esto, leyendo el archivo directamente en el destino, sin hacer una copia adicional).
– Jerry Ataúd
21 de septiembre de 2012 a las 15:11
“Contiguo no es requerido por el estándar” – sí lo es, de forma indirecta. Tan pronto como use op[] en la cadena, debe fusionarse en un búfer grabable contiguo, por lo que se garantiza que es seguro escribir en &str[0] si .resize() lo suficientemente grande primero. Y en C++ 11, la cadena simplemente siempre es contigua.
–Tino Didriksen
19 de julio de 2013 a las 17:22
Enlace relacionado: ¿Cómo leer un archivo en C++? — puntos de referencia y discute los diversos enfoques. Y si,
rdbuf
(el de la respuesta aceptada) no es el más rápido,read
es.– leyendas2k
27 de noviembre de 2014 a las 4:24