¿Cuál es la verdadera razón para no usar el bit EOF como nuestra condición de extracción de flujo?

Inspirado por mi pregunta anterior

Un error común para los nuevos progtwigdores de C ++ es leer de un archivo con algo como:

std::ifstream file("foo.txt"); std::string line; while (!file.eof()) { file >> line; // Do something with line } 

A menudo informan que la última línea del archivo se leyó dos veces. La explicación común para este problema (que he dado antes) es algo así como:

La extracción solo establecerá el bit EOF en la transmisión si intenta extraer el final de archivo, no si su extracción simplemente se detiene al final del archivo. file.eof() solo le dirá si la lectura anterior tocó el final del archivo y no si el siguiente lo hará. Después de extraer la última línea, el bit EOF aún no está configurado y la iteración se produce una vez más. Sin embargo, en esta última iteración, la extracción falla y la line sigue teniendo el mismo contenido que antes, es decir, la última línea está duplicada.

Sin embargo, la primera oración de esta explicación es incorrecta, por lo que la explicación de lo que está haciendo el código también es incorrecta.

La definición de funciones de entrada formateadas (cuyo operator>>(std::string&) es) define la extracción como el uso de rdbuf()->sbumpc() o rdbuf()->sgetc() para obtener caracteres de entrada. Establece que si cualquiera de estas funciones devuelve traits::eof() , entonces se establece el bit EOF:

Si rdbuf()->sbumpc() o rdbuf()->sgetc() devuelve traits::eof() , la función de entrada, salvo que se indique explícitamente lo contrario, completa sus acciones y establece el estado de setstate(eofbit) , que puede arrojar ios_base::failure (27.5.5.4), antes de regresar.

Podemos ver esto con el ejemplo simple que usa std::stringstream lugar de un archivo (ambos son flujos de entrada y se comportan de la misma manera al extraer):

 int main(int argc, const char* argv[]) { std::stringstream ss("hello"); std::string result; ss >> result; std::cout << ss.eof() << std::endl; // Outputs 1 return 0; } 

Aquí está claro que la extracción individual obtiene hello de la cadena y establece el bit EOF en 1.

Entonces, ¿qué pasa con la explicación? ¿Qué diferencia hay entre los archivos que hace que !file.eof() haga que la última línea se duplique? ¿Cuál es la verdadera razón por la que no deberíamos usar !file.eof() como nuestra condición de extracción?

Sí, extraer de un flujo de entrada establecerá el bit EOF si la extracción se detiene al final del archivo, como lo demuestra el ejemplo de std::stringstream . Si fuera así de simple, el ciclo con !file.eof() como su condición funcionaría bien en un archivo como:

 hello world 

La segunda extracción comería todo el world , se detendría al final del archivo y, en consecuencia, establecería el bit EOF. La siguiente iteración no ocurriría.

Sin embargo, muchos editores de texto tienen un secreto sucio. Te mienten cuando guardas un archivo de texto, incluso tan simple como eso. Lo que no te dicen es que hay un \n oculto al final del archivo. Cada línea del archivo termina con un \n , incluido el último. Entonces el archivo realmente contiene:

 hello\nworld\n 

Esto es lo que hace que la última línea se duplique al usar !file.eof() como condición. Ahora que sabemos esto, podemos ver que la segunda extracción se comerá world deteniéndose en \n y no configurando el bit EOF (porque aún no hemos llegado allí). El ciclo repetirá por tercera vez pero la próxima extracción fallará porque no encuentra una cadena para extraer, solo espacios en blanco. La cadena se queda con su valor anterior todavía dando vueltas y así obtenemos la línea duplicada.

No experimentas esto con std::stringstream porque lo que std::stringstream en la transmisión es exactamente lo que obtienes. No hay \n al final de std::stringstream ss("hello") , a diferencia del archivo. Si hiciera std::stringstream ss("hello\n") , experimentaría el mismo problema de línea duplicada.

Entonces, por supuesto, podemos ver que nunca debemos usar !file.eof() como la condición al extraer de un archivo de texto, pero ¿cuál es el verdadero problema aquí? ¿Por qué nunca deberíamos usar eso como nuestra condición, independientemente de si estamos extrayendo de un archivo o no?

El verdadero problema es que eof() no nos da ni idea de si la próxima lectura fallará o no . En el caso anterior, vimos que aunque eof() era 0, la siguiente extracción falló porque no había una cadena para extraer. La misma situación sucedería si no asociamos una secuencia de archivos con ningún archivo o si la transmisión estaba vacía. El bit EOF no estaría configurado, pero no hay nada que leer. No podemos simplemente seguir adelante ciegamente y extraer del archivo simplemente porque eof() no está establecido.

El uso de while (std::getline(...)) y las condiciones relacionadas funciona perfectamente porque justo antes de que comience la extracción, la función de entrada formateada comprueba si se ha configurado alguno de los bits defectuosos, fallidos o EOF. Si alguno de ellos lo es, inmediatamente termina, estableciendo el bit de falla en el proceso. También fallará si encuentra el final del archivo antes de que encuentre lo que quiere extraer, configurando los bits eof y fail.


Nota: Puede guardar un archivo sin el extra \n en vim si lo hace :set noeol y :set binary antes de guardar.

Tu pregunta tiene algunas concepciones falsas. Usted da una explicación:

“La extracción solo configurará el bit EOF en la transmisión si intenta extraer el final de archivo, no si su extracción simplemente se detiene al final del archivo”.

Luego, asegúrate de que “está mal y la explicación de lo que está haciendo el código también es incorrecta”.

En realidad, es correcto. Veamos un ejemplo….

Al leer en std::string

 std::istringsteam iss('abc\n'); std::string my_string; iss >> my_string; 

… por defecto y como en su operator>> preguntas operator>> está leyendo caracteres hasta que encuentre espacios en blanco o EOF. Asi que:

  • leyendo de 'abc\n' -> una vez que se encuentra '\n' , no “intenta extraer el final de archivo”, sino que “simplemente se detiene en [EOF]”, y eof() ganó ” t devuelve true ,
  • leyendo de 'abc' lugar -> es el bash de extraer el final de archivo que descubre el final del contenido de la string , por lo que eof() devolverá true .

Del mismo modo, el análisis de '123' en un int establece eof() porque el análisis no sabe si habrá otro dígito y trata de seguir leyéndolos, eof() . El análisis '123 ' de un int no establecerá eof() .

Fundamentalmente, analizar ‘a’ en un char no establecerá eof() porque el espacio en blanco al final no es necesario para saber que el análisis está completo: una vez que se lee un personaje, no se intenta encontrar otro personaje y el eof() no está ‘Encontrado. (Por supuesto, un análisis posterior de la misma secuencia llega a eof ).

Está claro [para stringstream “hello” >> std :: string] que la extracción única obtiene hello de la cadena y establece el bit EOF en 1. Entonces, ¿qué hay de malo con la explicación? ¿Qué diferencia hay entre los archivos que hace que! File.eof () haga que la última línea se duplique? ¿Cuál es la verdadera razón por la que no deberíamos usar! File.eof () como nuestra condición de extracción?

La razón es la anterior … que los archivos tienden a terminarse con un caracter ‘\ n’, y cuando son medios getline o >> std::string devuelven el último token que no es de espacio en blanco sin necesidad de “intentar extraer el fin de archivo “(para usar su frase).