¿Por qué iostream::eof dentro de una condición de bucle (es decir, `` while (!stream.eof())`) se considera incorrecto?

Resuelto MAK asked hace 13 años • 5 respuestas

Acabo de encontrar un comentario en esta respuesta que dice que usar iostream::eofen una condición de bucle es "casi con certeza incorrecto". Generalmente uso algo como while(cin>>n)- que supongo que comprueba implícitamente el EOF.

¿ Por qué la verificación de eof explícitamente es while (!cin.eof())incorrecta?

¿ En qué se diferencia de usarlo scanf("...",...)!=EOFen C (que uso a menudo sin problemas)?

MAK avatar Apr 09 '11 19:04 MAK
Aceptado

Porque iostream::eofsolo regresará true después de leer el final de la transmisión. 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 transmisión):

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
}

Contra 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 sobre tu 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
Xeo avatar Apr 09 '2011 12:04 Xeo

En pocas palabras: con un manejo adecuado de los espacios en blanco, lo siguiente es cómo eofse puede utilizar (e incluso ser más confiable que fail()para la verificación de errores):

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

( Gracias Tony D por 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 ) .


Al principal argumento en contra del uso eof()parece faltarle una sutileza importante sobre el papel del espacio en blanco. Mi propuesta es que verificar eof()explícitamente no solo no " siempre es incorrecto " (lo que parece ser una opinión predominante en esta y otras preguntas similares de Stack Overflow), sino que con el manejo adecuado de los espacios en blanco, proporciona un error más limpio y confiable. manejo, y es la solución siempre correcta (aunque no necesariamente la más concisa).

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

int data;
while(in >> data) {  /* ... */ }

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

El fallo debido al intento de lectura más allá de eof se toma como condición de terminación. Esto significa que no existe una manera fácil de distinguir entre una transmisión exitosa y una que realmente falla por razones distintas a eof. Tome las siguientes transmisiones:

  • 1 2 3 4 5<eof>
  • 1 2 a 3 4 5<eof>
  • a<eof>

while(in>>data)termina con un conjunto failbitpara las tres entradas. En el primero y en el tercero, eofbittambién se fija. Entonces, más allá del ciclo, se necesita una lógica adicional muy fea para distinguir una entrada adecuada (primera) de las incorrectas (segunda y tercera).

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, sea el correcto. Su propósito no es un simple terminador de bucle while .

Hasta ahora todo bien, pero ¿qué sucede si hay un espacio final en la secuencia? ¿Cuál parece ser la principal preocupación eof()como terminador?

No necesitamos renunciar a nuestro manejo de errores; simplemente come el espacio en blanco:

while( !in.eof() )
{
   int data;
   in >> data >> ws; // Eat white space with 'std::ws'
   if ( in.fail() ) /* Handle with 'break' or 'throw' */;
   // Now use data
}

std::wsomite cualquier espacio final potencial (cero o más) en la secuencia al configurar eofbit, y nofailbit . Entonces, in.fail()funciona como se esperaba, siempre que haya al menos un dato para leer. Si también se aceptan secuencias completamente en blanco, 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 es incorrecta, sino que permite localizar los datos dentro del alcance y proporciona una separación más clara entre la verificación de errores y el negocio habitual. Dicho esto, while(!fail)es indiscutiblemente un modismo más común y conciso, y puede preferirse en escenarios simples (datos únicos por tipo de lectura).

sly avatar Nov 23 '2012 23:11 sly

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

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

Aquí el problema es que no puede prescindir some work on nde comprobar primero si la lectura de la secuencia se realizó correctamente, porque si no se realizó correctamente, se some work on nproduciría un resultado no deseado.

El punto es que, eofbit, badbito failbitse configuran después de intentar leer la secuencia. Entonces, si stream >> nfalla, entonces eofbit, badbito failbitse establece inmediatamente, por lo que es más idiomático si escribe while (stream >> n), porque el objeto devuelto streamse convierte falsesi hubo algún error en la lectura de la secuencia y, en consecuencia, el bucle se detiene. Y se convierte truesi la lectura fue exitosa y el ciclo continúa.

Sarfaraz Nawaz avatar Apr 09 '2011 12:04 Sarfaraz Nawaz

Lo importante que debe recordar es que esto inFile.eof()no sucederá Truehasta que falle un intento de lectura, porque haya llegado al final del archivo. Entonces, en este ejemplo, obtendrá un error.

while (!inFile.eof()){
    inFile >> x;
        process(x);
}

La forma de hacer que este bucle sea correcto es combinar la lectura y la verificación en una sola operación, así

while (inFile >> x) 
    process(x); 

Por convención, operator>>devuelve la secuencia de la que leemos y una prueba booleana en una secuencia regresa Falsecuando la secuencia falla (como al llegar al final del archivo).

Entonces esto nos da la secuencia correcta:

  • leer
  • probar si la lectura tiene éxito
  • si y sólo si la prueba tiene éxito, procesa lo que hemos leído

Si encuentra algún otro problema que le impide leer el archivo correctamente, no podrá acceder a eof()él. Por ejemplo, veamos algo como esto

int x; 
while (!inFile.eof()) { 
    inFile >> x; 
    process(x);
} 

Repasemos el funcionamiento del código anterior, con un ejemplo.

  • Suponga que el contenido del archivo es '1', '2', '3', 'a', 'b'.
  • El bucle leerá el 1, 2 y 3 correctamente.
  • Entonces llegará a a.
  • Cuando intenta extraer acomo int, fallará.
  • La transmisión ahora se encuentra en un estado fallido, hasta que clearla transmitamos, o a menos que la transmitamos, todos los intentos de leerla fallarán.
  • Pero, cuando probamos eof(), devolverá False, porque no estamos al final del archivo, porque todavía hay aesperando ser leído.
  • El bucle seguirá intentando leer el archivo y fallará cada vez, por lo que nunca llega al final del archivo.
  • Entonces, el bucle anterior se ejecutará para siempre.

Pero, si usamos un bucle como este, obtendremos el resultado requerido.

while (inFile >> x)
    process(x);

En este caso, la secuencia se convertirá Falseno solo en caso de que finalice el archivo, sino también en caso de una conversión fallida, como la que ano podemos leer como un número entero.

Deepthi Tabitha Bennet avatar Feb 19 '2022 17:02 Deepthi Tabitha Bennet