¿Cómo leo un archivo en un archivo 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 cumplir con los estándares, ser 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 crear el tamaño del archivo, cambiar el tamaño std::string
y fread()
el std::string
's const_cast<char*>()
' ed data()
. Esto requiere que los std::string
datos 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 std::string
tamaño del archivo puede no ser igual al tamaño del archivo.
Se podrían construir soluciones portátiles totalmente correctas, que cumplan con los estándares, utilizando std::ifstream
's rdbuf()
en ay std::ostringstream
desde allí en a std::string
. Sin embargo, esto podría copiar los datos de la cadena y / o reasignar innecesariamente la memoria.
- ¿Son todas las implementaciones de biblioteca estándar relevantes lo suficientemente inteligentes como para evitar toda sobrecarga innecesaria?
- ¿Hay otra forma de hacerlo?
- ¿Perdí alguna función oculta de Boost que ya proporciona la funcionalidad deseada?
void slurp(std::string& data, bool is_binary)
rdbuf
(el que está en la respuesta aceptada) no es el más rápidoread
.Respuestas:
Una forma es vaciar el búfer de flujo en un flujo de memoria separado y luego convertirlo a
std::string
:Esto es muy conciso. Sin embargo, como se señaló en la pregunta, esto realiza una copia redundante y desafortunadamente no hay forma fundamental de eludir esta copia.
Lamentablemente, la única solución real que evita las copias redundantes es hacer la lectura manualmente en un bucle. Dado que C ++ ahora tiene cadenas contiguas garantizadas, se podría escribir lo siguiente (≥C ++ 14):
fuente
string
. Es decir, requiere el doble de memoria que algunas de las otras opciones. (No hay forma de mover el búfer). Para un archivo grande, esto sería una penalización significativa, quizás incluso causando una falla de asignación.Vea esta respuesta en una pregunta similar.
Para su comodidad, estoy volviendo a publicar la solución de CTT:
Esta solución resultó en tiempos de ejecución aproximadamente 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 mmap'ing el archivo;)
fuente
ifs.seekg(0, ios::end)
antestellg
? justo después de abrir un archivo, el puntero de lectura está al principio y, por lo tanto,tellg
devuelve ceronullptr
por&bytes[0]
ios::ate
, así que creo que una versión con movimiento explícito hasta el final sería más legibleLa variante más corta: Live On Coliru
Requiere el encabezado
<iterator>
.Hubo algunos informes de que este método es más lento que preasignar la cadena y usarla
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 ser altamente dependiente del compilador.fuente
Utilizar
o algo muy cercano. No tengo una referencia stdlib abierta para verificar dos veces.
Sí, entiendo que no escribí la
slurp
función como se me pidió.fuente
operator>>
lee en astd::basic_streambuf
, consumirá (lo que queda de) el flujo de entrada, por lo que el bucle es innecesario.Si tiene C ++ 17 (std :: filesystem), también existe de esta manera (que obtiene el tamaño del archivo en
std::filesystem::file_size
lugar deseekg
ytellg
):Nota : es posible que necesite usar
<experimental/filesystem>
ystd::experimental::filesystem
si su biblioteca estándar aún no es totalmente compatible con C ++ 17. También podría ser necesario reemplazarresult.data()
con&result[0]
si no soporta std :: basic_string datos no const .fuente
boost::filesystem
para que también puedas usar boost si no tienes c ++ 17No tengo suficiente reputación para comentar directamente sobre las respuestas que utilizo
tellg()
.Tenga en cuenta que
tellg()
puede devolver -1 en caso de error. Si pasa el resultadotellg()
como un parámetro de asignación, primero debe verificar el resultado.Un ejemplo del problema:
En el ejemplo anterior, si
tellg()
encuentra un error, devolverá -1. La conversión implícita entre firmado (es decir, el resultado detellg()
) y sin signo (es decir, el arg alvector<char>
constructor) se traducirá en un su vector erróneamente la asignación de una muy gran número de bytes. (Probablemente 4294967295 bytes, o 4GB.)Modificando la respuesta de paxos1977 para tener en cuenta lo anterior:
fuente
Esta solución agrega verificación de errores al método basado en rdbuf ().
Estoy agregando esta respuesta porque agregar la comprobació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 stringstream (
str_stream << file_stream.rdbuf()
). El problema es que esto establece el bitbit de secuencia de cadena cuando no se insertan caracteres. Eso puede ser debido a un error o puede ser debido 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 para insertar caracteres y la "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 verificación de errores asociados.
La 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 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 verifique la condición de falla
file_stream.fail() && !file_stream.eof()
.Es importante destacar que, cuando se
file_stream >> str_stream.rdbuf()
encuentra con 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.fuente
Algo como esto no debería ser tan malo:
La ventaja aquí es que hacemos la reserva primero para no tener 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 tomar todo el buf de lectura y luego llamar al flujo inferior.
fuente
Aquí hay una versión que usa la nueva biblioteca del sistema de archivos con una comprobación de errores razonablemente robusta:
fuente
infile.open
también puede aceptarstd::string
sin convertir con.c_str()
filepath
no es unstd::string
, es unstd::filesystem::path
. Resulta questd::ifstream::open
puede aceptar uno de esos también.std::filesystem::path
es implícitamente convertible astd::string
::open
función miembrostd::ifstream
que aceptastd::filesystem::path
opera como si el::c_str()
método fuera llamado en la ruta. El subyacente::value_type
de las rutas estáchar
bajo POSIX.Puede usar la función 'std :: getline' y especificar 'eof' como delimitador. Sin embargo, el código resultante es un poco oscuro:
fuente
Nunca escriba en el buffer stst :: string's const char *. ¡Nunca jamás! Hacerlo es un gran error.
Reserve () espacio para toda la cadena en su std :: string, lea fragmentos de su archivo de tamaño razonable en un búfer y añádalo (). El tamaño que deben tener los fragmentos 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).
fuente
std::string
búfer; y creo que funcionó correctamente en todas las implementaciones reales antes de esostd::string::data()
método no constante para modificar el búfer de cadenas directamente sin recurrir a trucos como&str[0]
.uso:
fuente
Una función actualizada que se basa en la solución de CTT:
Hay dos diferencias importantes:
tellg()
no se garantiza que devuelva el desplazamiento en bytes desde el comienzo del archivo. En cambio, como señaló Puzomor Croacia, es más un token que se puede usar dentro de las llamadas de fstream.gcount()
sin embargo , devuelve la cantidad de bytes sin formato extraídos por última vez. Por lo tanto, abrimos el archivo, extraemos y descartamos todo su contenido conignore()
para obtener el tamaño del archivo, y construimos la cadena de salida en función de eso.En segundo lugar, evitamos tener que copiar los datos del archivo de un
std::vector<char>
a unstd::string
escribiendo directamente en la cadena.En términos de rendimiento, este debería ser el más rápido absoluto, asignando la cadena del tamaño apropiado con anticipación y llamando
read()
una vez. Como dato interesante, usarignore()
y encountg()
lugar deate
ytellg()
en gcc se compila casi a la misma cosa , poco a poco.fuente
ifs.seekg(0)
lugar deifs.clear()
(entonces funciona).fuente