¿Cuál es la mejor manera de leer un archivo completo en std :: string en C ++?

¿Cómo leo un archivo en std::string , es decir, leo todo el archivo a la vez?

El llamador debe especificar el modo de texto o binario. La solución debe ser estándar, portátil y eficiente. No debería copiar innecesariamente los datos de la cadena, y debería evitar reasignaciones de memoria mientras lee la cadena.

Una forma de hacerlo sería establecer el tamaño del archivo, cambiar el tamaño de std::string y fread() en std::string const_cast() ‘ed data() . Esto requiere que los datos de std::string sean contiguos, lo cual 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 tamaño de std::string puede no ser igual al tamaño del archivo.

Se podrían construir soluciones totalmente correctas, compatibles con el estándar y portátiles usando std::ifstream ‘s rdbuf() en std::ostringstream y de allí en std::string . Sin embargo, esto podría copiar los datos de cadena y / o reasignar innecesariamente la memoria. ¿Son todas las implementaciones de bibliotecas estándar relevantes lo suficientemente inteligentes como para evitar todos los gastos generales innecesarios? ¿Hay alguna otra forma de hacerlo? ¿Perdí alguna función oculta de Boost que ya proporciona la funcionalidad deseada?

Por favor, muestre su sugerencia sobre cómo implementarlo.

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

teniendo en cuenta la discusión anterior.

Y el más rápido (que yo sepa, descontando los archivos mapeados en memoria):

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

Esto requiere el encabezado adicional para la secuencia de cadenas. (El static_cast es necesario ya que el operator < < devuelve un ostream& simple y antiguo, pero sabemos que en realidad es un stringstream& lo que el elenco es seguro).

Dividir en varias líneas, mover el temporal en una variable, obtenemos un código más legible:

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

O, una vez más en una sola línea:

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

Vea esta respuesta en una pregunta similar.

Para su comodidad, estoy volviendo 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 bytes(fileSize); ifs.read(bytes.data(), fileSize); return string(bytes.data(), fileSize); } 

Esta solución dio como resultado tiempos de ejecución un 20% más rápidos que las otras respuestas presentadas aquí, al tomar el promedio de 100 carreras 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 mmap’ing el archivo;)

La variante más corta: Live On Coliru

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

Requiere el encabezado .

Hubo algunos informes de que este método es más lento que la asignación previa de la cadena y el uso de std::istream::read . Sin embargo, en un comstackdor moderno con optimizaciones habilitadas, este ya no parece ser el caso, aunque el rendimiento relativo de varios métodos parece ser altamente dependiente del comstackdor.

Utilizar

 #include  #include  #include  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 stdlib abierta para verificarme a mí mismo.

Sí, entiendo que no escribí la función de slurp como se me pidió.

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á pasando el resultado de tellg() como parámetro de asignación, primero debe verificar el resultado.

Un ejemplo del problema:

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

En el ejemplo anterior, si tellg() encuentra un error, devolverá -1. La conversión implícita entre firmado (es decir, el resultado de tellg() ) y unsigned (es decir, el arg al vector constructor) dará como resultado que su vector asigne erróneamente una gran cantidad de bytes. (Probablemente 4294967295 bytes, o 4 GB)

Modificando la respuesta de paxos1977 a la cuenta de 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 bytes(fileSize); ifs.read(&bytes[0], fileSize); return string(&bytes[0], fileSize); } 

Nunca escriba en el buffer const char * de std :: string. ¡Nunca jamás! Hacerlo es un gran error.

Reserve () espacio para toda la cadena en su std :: cadena, lea los fragmentos de su archivo de tamaño razonable en un búfer y añádalo (). El tamaño de los trozos depende del tamaño del archivo de entrada. Estoy bastante seguro de que todos los demás mecanismos portátiles y compatibles con STL harán lo mismo (aunque pueden parecer más bonitos).

Algo como esto no debería ser tan malo:

 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()); } 

La ventaja aquí es que hacemos la reserva primero, así que no tendremos que hacer crecer la cadena mientras leemos las cosas. La desventaja es que lo hacemos char por char. Una versión más inteligente podría capturar toda la lectura buf y luego llamar a subdesbordamiento.

Puede usar la función ‘std :: getline’ y especificar ‘eof’ como delimitador. El código resultante es un poco oscuro, sin embargo:

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

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 lugar de seekg y 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 : es posible que deba utilizar y std::experimental::filesystem si su biblioteca estándar aún no es totalmente compatible con C ++ 17. Es posible que también necesite reemplazar result.data() con &result[0] si no es compatible con los datos no const std :: basic_string .

Esta solución agrega comprobació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 utiliza el operador de inserción de str_stream < < file_stream.rdbuf() ( str_stream < < file_stream.rdbuf() ). El problema es que esto establece el bit de fallas de la cadena de caracteres cuando no se insertan caracteres. Eso puede deberse a un error o puede deberse a que el archivo está vacío. Si verifica las fallas inspeccionando el failbit, encontrará un falso positivo cuando lea un archivo vacío. ¿Cómo se desajusta la falla legítima para insertar caracteres y "falla" para 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 comprobación de errores asociados.

Comprobando 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 el ostringstream ni en el 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, compruebe la condición de file_stream.fail() && !file_stream.eof() .

Es importante destacar que cuando file_stream >> str_stream.rdbuf() encuentra una falla legítima, nunca debería establecer eofbit (de acuerdo con mi comprensión de la especificación). Eso significa que la verificación anterior es suficiente para detectar fallas legítimas.

¿Qué pasa si está sorbiendo un archivo de 11K, luego tiene que hacerlo en una serie de fragmentos, por lo que debe usar algo como std :: vector para sorber en grandes trozos de cadenas.