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

Acabo de encontrar un comentario en esta respuesta diciendo que el uso de iostream::eof en una condición de bucle es "casi ciertamente malo". Yo generalmente uso algo como while(cin>>n) - que supongo que implícitamente cheques para las expresiones del FOLCLORE.

¿Por qué es la comprobación de ef de forma explícita el uso de while (!cin.eof()) mal?

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

  • Respuesta seleccionada
    Xeo
    9 de abril de 2011

    Porque iostream::eof sólo devolverá true después de leer el final de la secuencia. Sí no indicar, que la próxima lectura será el final de la secuencia.

    Considere esto (y asumir entonces 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
    }
    

    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 el mismo

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

    y no es el mismo que

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

3 Respuestas

  • Nawaz
    9 de abril de 2011

    Porque si los programadores no escribir while(stream >> n), que posiblemente escribir esto:

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

    Aquí el problema es que no se puede hacer some work on n sin comprobar primero si la secuencia de lectura fue un éxito, porque si no tuvo éxito, su some work on n produciría un resultado no deseado.

    El punto es que, eofbit, badbit o failbit se establecen después de un intento de lectura de la secuencia. Así que si stream >> n falla, entonces eofbit, badbit o failbit es el conjunto de inmediato, así que es más idiomático, si usted escribe while (stream >> n), porque el objeto devuelto stream convierte a false si hubo algún fallo en la lectura de la secuencia y, en consecuencia, el bucle se detiene. Y que se convierte en true si la lectura fue un éxito y el bucle continúa.

  • sly
    24 de noviembre de 2012

    Parte inferior de la línea de arriba: Con el manejo adecuado de espacio en blanco, el siguiente es cómo eof pueden ser utilizados (e incluso, ser más fiable que la de 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
    }    
    

    (Gracias Tony D por la sugerencia para resaltar la respuesta. Ver su comentario a continuación un ejemplo de por qué esto es más robusto.)


    El principal argumento en contra del uso de eof() parece estar perdiendo una importante sutileza sobre el papel del espacio en blanco. Mi propuesta es que, la comprobación de eof() explícitamente no sólo no es "siempre es malo", lo cual parece ser una opinión preponderante en esta y similares PARA hilos --, pero con el manejo adecuado de espacio en blanco, se ofrece para una mejor y más confiable en el manejo de errores, y es la siempre correcta solución (aunque no necesariamente la tersest).

    Para resumir lo que se propone como la "correcta" terminación y leer el orden es el siguiente:

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

    El fracaso debido a un intento de lectura más allá de la eof se considera la condición de terminación. Esto significa que no hay ninguna manera fácil de distinguir entre un exitoso corriente y que realmente falla por otras razones que la ef. Tome las siguientes corrientes:

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

    while(in>>data) termina con un conjunto failbit para todos los tres de entrada. En la primera y la tercera, eofbit también se establece. Así que pasado el bucle uno necesita muy feo lógica adicional para distinguir una entrada correcta (1) inadecuado (de 2ª y 3ª).

    Considerando, que tome las siguientes:

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

    Aquí, in.fail() comprueba que siempre hay algo para leer, es la correcta. Su propósito no es un simple bucle while terminator.

    Hasta aquí bien, pero ¿qué pasa si no hay espacio al final de la secuencia -- lo que suena como el tema de mayor preocupación en contra eof() como terminator?

    No tenemos que entregar nuestra manejo de errores; sólo se comen 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 potencial (cero o más) espacio al final de la secuencia, mientras que la configuración de la eofbit, y no la failbit. Así, in.fail() funciona como se espera, siempre y cuando al menos uno de los datos a leer. Si todo en blanco corrientes 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: Un bien construido while(!eof) es no sólo posible, y no se equivocan, pero permite que los datos que se localiza dentro de alcance, y proporciona una separación más clara de comprobación de error de los negocios como de costumbre. Que siendo dicho, while(!fail) es sin lugar a dudas más comunes y conciso de modismos, y puede ser preferible en simples (una sola datos por leer tipo de) de los escenarios.

  • melpomene
    4 de mayo de 2019

    Las otras respuestas han explicado por qué la lógica que está mal en while (!stream.eof()) y cómo solucionarlo. Quiero centrarme en algo diferente:

    ¿por qué es la comprobación de ef de forma explícita el uso de iostream::eof mal?

    En términos generales, la comprobación de eof sólo es equivocada porque la secuencia de extracción (>>) pueden fallar sin golpear al final del archivo. Si usted tiene por ejemplo, int n; cin >> n; y la secuencia contiene hello, entonces h no es un dígito válido, por lo que la extracción se producirá sin necesidad de llegar a la final de la entrada.

    Este problema, combinado con la lógica general de error de comprobación de la corriente de estado antes de intentar leer de ella, lo cual significa que para N de entrada de los elementos que el bucle se ejecute N+1 veces, conduce a los síntomas siguientes:

    • Si la secuencia está vacía, el bucle se ejecuta una vez. >> fallará (no hay ninguna entrada para ser leído) y todas las variables que se suponía que iban a configurar (por stream >> x) son en realidad sin inicializar. Esto conduce a la basura datos a ser procesados, que puede manifestarse como falta de sentido de los resultados (a menudo, grandes números).

    • Si la secuencia no está vacía, el bucle se ejecute de nuevo después de la última entrada válida. Ya que en la última iteración todos los >> operaciones no, las variables son propensos a mantener su valor a partir de la iteración anterior. Esto puede manifestarse como "la última línea se imprime dos veces" o "el último registro de entrada se procesa dos veces".

    • Si la secuencia contiene datos con formato incorrecto, pero sólo puedes comprobar .eof, que terminan con un bucle infinito. >> fallará extraer todos los datos de la secuencia, por lo que el bucle tiradas en el lugar, sin llegar nunca a la final.


    Para recapitular: La solución es poner a prueba el éxito de la >> operación en sí misma, no usar un .eof() método: while (stream >> n >> m) { ... }, como en C que prueba el éxito de la scanf se llama a sí mismo: while (scanf("%d%d", &n, &m) == 2) { ... }.