¿Por qué iostream :: eof está dentro de una condición de bucle considerada incorrecta?

Acabo de encontrar un comentario en esta respuesta que dice que el uso de iostream::eof en una condición de bucle es “casi seguro que está mal”. Por lo general, uso algo como while(cin>>n) , que supongo que comprueba implícitamente EOF, ¿por qué se comprueba si eof utiliza iostream::eof wrong de forma iostream::eof ?

¿Cómo es diferente de usar scanf("...",...)!=EOF en C (que a menudo uso sin problemas)?

Debido a que iostream::eof solo volverá true después de leer el final de la secuencia. No indica que la próxima lectura será el final de la transmisión.

Considere esto (y suponga que la próxima lectura será al final de la secuencia):

 while(!inStream.eof()){ int data; // yay, not end of stream yet, now read ... inStream >> data; // oh crap, now we read the end and *only* now the eof bit will be set (as well as the fail bit) // do stuff with (now uninitialized) data } 

En contra de esto:

 int data; while(inStream >> data){ // when we land here, we can be sure that the read was successful. // if it wasn't, the returned stream from operator>> would be converted to false // and the loop wouldn't even be entered // do stuff with correctly initialized data (hopefully) } 

Y en su segunda pregunta: Porque

 if(scanf("...",...)!=EOF) 

es lo mismo que

 if(!(inStream >> data).eof()) 

y no es lo mismo que

 if(!inStream.eof()) inFile >> data 

Conclusión: con un manejo adecuado del espacio en blanco, se puede usar eof (e incluso, ser más confiable que fail() para la comprobación de errores):

 while( !(in>>std::ws).eof() ) { int data; in >> data; if ( in.fail() ) /* handle with break or throw */; // now use data } 

( Agradece a Tony D la sugerencia de resaltar la respuesta. Consulte su comentario a continuación para ver un ejemplo de por qué esto es más sólido ) .


El principal argumento en contra del uso de eof() parece faltar una sutileza importante sobre el papel del espacio en blanco. Mi proposición es que comprobar eof() explícitamente no solo no es ” siempre incorrecto “, lo que parece ser una opinión predominante en este y otros hilos SO similares, sino que con un manejo adecuado del espacio en blanco, proporciona un limpiador y un manejo de errores más confiable, y es la solución siempre correcta (aunque no necesariamente la más estricta).

Para resumir lo que se sugiere como la terminación “adecuada” y el orden de lectura es la siguiente:

 int data; while(in >> data) { /* ... */ } // which is equivalent to while( !(in >> data).fail() ) { /* ... */ } 

La falla debida al bash de lectura más allá de eof se toma como la condición de terminación. Esto significa que no existe una manera fácil de distinguir entre una secuencia exitosa y otra que realmente falla por otros motivos distintos de eof. Toma las siguientes transmisiones:

  • 1 2 3 4 5
  • 1 2 a 3 4 5
  • a

while(in>>data) termina con un set failbit para las tres entradas. En el primero y el tercero, también se establece eofbit . Entonces, más allá del ciclo, se necesita una lógica extra muy fea para distinguir una entrada adecuada (1ra) de las incorrectas (2da y 3ra).

Considerando que, tome lo siguiente:

 while( !in.eof() ) { int data; in >> data; if ( in.fail() ) /* handle with break or throw */; // now use data } 

Aquí, in.fail() verifica que mientras haya algo para leer, es el correcto. Su propósito no es un mero while-loop terminator.

Hasta ahora todo va bien, pero ¿qué ocurre si hay un espacio en la secuencia – lo que parece ser la principal preocupación contra eof() como terminator?

No necesitamos renunciar a nuestro manejo de errores; acaba de comer el espacio en blanco:

 while( !in.eof() ) { int data; in >> data >> ws; // eat whitespace with std::ws if ( in.fail() ) /* handle with break or throw */; // now use data } 

std::ws omite cualquier espacio potencial (cero o más) en la secuencia mientras configura el eofbit , y no el failbit . Por lo tanto, in.fail() funciona como se espera, siempre que haya al menos un dato para leer. Si las transmisiones en blanco también son aceptables, entonces la forma correcta es:

 while( !(in>>ws).eof() ) { int data; in >> data; if ( in.fail() ) /* handle with break or throw */; /* this will never fire if the eof is reached cleanly */ // now use data } 

Resumen: una construcción adecuada while(!eof) no solo es posible y no está mal, sino que permite que los datos se encuentren dentro del scope, y proporciona una separación más clara de la comprobación de errores de la actividad habitual. Dicho esto, while(!fail) es indiscutiblemente una expresión más común y concisa, y puede preferirse en escenarios simples (datos individuales por tipo de lectura).

Porque si los progtwigdores no escriben while(stream >> n) , posiblemente escriban esto:

 while(!stream.eof()) { stream >> n; //some work on n; } 

Aquí el problema es que no se puede hacer some work on n sin antes verificar si la lectura de la secuencia fue exitosa, porque si no fue exitosa, su some work on n produciría un resultado no deseado.

El punto es que eofbit , badbit o failbit se configuran después de que se intenta leer de la transmisión. Entonces, si stream >> n falla, entonces eofbit , badbit o failbit se establece inmediatamente, por lo que es más idiomático si se escribe while (stream >> n) , porque el stream objetos devuelto se convierte en false si hubo alguna falla en la lectura de la secuencia y, en consecuencia, el ciclo se detiene. Y se convierte en true si la lectura fue exitosa y el ciclo continúa.

 1 while (!read.fail()) { 2 cout < < ch; 3 read.get(ch); 4 } 

Si usa la línea 2 en 3 y la línea 3 en 2, obtendrá ch impresa dos veces. Entonces cout antes de leer.