Question Pourquoi iostream :: eof est-il dans une condition de boucle considérée comme incorrecte?


Je viens de trouver un commentaire dans ce répondre en disant que l'utilisation iostream::eof dans une condition de boucle est "presque certainement faux". J'utilise généralement quelque chose comme while(cin>>n) - ce que je suppose implicitement vérifie EOF, pourquoi vérifie-t-on explicitement iostream::eof faux?

En quoi est-ce différent d'utiliser scanf("...",...)!=EOF en C (que j'utilise souvent sans problème)?


482
2018-04-09 12:49


origine


Réponses:


Car iostream::eof ne reviendra que true  après lire la fin du flux. Cela fait ne pas indique que la prochaine lecture sera la fin du flux.

Considérez ceci (et supposons que la prochaine lecture aura lieu à la fin du flux):

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
}

Contre cela:

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)
}

Et sur votre deuxième question: Parce que

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

est le même que

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

et ne pas le même que

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

437
2018-04-09 12:58



Haut de gamme:  Avec une bonne manipulation des espaces blancs, voici comment eof peut être utilisé (et même, être plus fiable que fail() pour la vérification des erreurs):

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

(Merci Tony D pour la suggestion de mettre en évidence la réponse. Voir son commentaire ci-dessous pour un exemple expliquant pourquoi cela est plus robuste.)


L'argument principal contre l'utilisation eof() semble manquer une subtilité importante sur le rôle des espaces blancs. Ma proposition est que, en vérifiant eof() explicitement n'est pas seulement "toujours faux"- qui semble être une opinion dominante dans ce SO et des threads similaires -, mais avec une bonne gestion de l'espace blanc, il fournit une gestion des erreurs plus propre et plus fiable, et est le toujours correct solution (bien que pas nécessairement le tersest).

Pour résumer ce qui est suggéré comme l'ordre de terminaison et de lecture "correct" est le suivant:

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

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

L'échec dû à la tentative de lecture au-delà de l'eof est considéré comme la condition de terminaison. Cela signifie qu'il n'y a pas de moyen facile de distinguer entre un flux réussi et celui qui échoue vraiment pour des raisons autres que l'eof. Prenez les flux suivants:

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

while(in>>data) se termine avec un ensemble failbit pour tout trois entrées Dans le premier et le troisième, eofbit est également défini. Donc passé la boucle on a besoin d'une logique supplémentaire très laide pour distinguer une entrée correcte (1ère) des mauvaises (2ème et 3ème).

Considérons que:

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

Ici, in.fail() vérifie que tant qu'il y a quelque chose à lire, c'est le bon. Son but n'est pas un simple terminateur en boucle.

Jusqu'ici tout va bien, mais que se passe-t-il s'il y a un espace à la traîne dans le flux? eof() en tant que terminateur?

Nous n'avons pas besoin de rendre notre gestion des erreurs; mange juste l'espace blanc:

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 ignore tout espace de fin potentiel (zéro ou plus) dans le flux tout en réglant eofbit, et pas le failbit. Alors, in.fail() fonctionne comme prévu, tant qu'il y a au moins une donnée à lire. Si les flux entièrement vierges sont également acceptables, la forme correcte est la suivante:

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
}

Résumé: Un correctement construit while(!eof) est non seulement possible et non erroné, mais permet également de localiser les données dans le périmètre et offre une séparation plus nette entre la vérification des erreurs et l'activité habituelle. Cela étant dit, while(!fail) est indiscutablement un idiome plus commun et laconique, et peut être préféré dans des scénarios simples (données uniques par type de lecture).


87
2017-11-23 23:30



Parce que si les programmeurs n'écrivent pas while(stream >> n), ils écrivent peut-être ceci:

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

Ici le problème est, vous ne pouvez pas faire some work on n sans vérifier au préalable si le flux lu a réussi, car s’il a échoué, votre some work on n produirait un résultat indésirable.

Tout le point est que, eofbit, badbit, ou failbit sont fixés après une tentative de lecture du flux. Donc si stream >> n échoue, puis eofbit, badbit, ou failbit est réglé immédiatement, donc c'est plus idiomatique si vous écrivez while (stream >> n), parce que l'objet retourné stream convertit en false s'il y a eu un échec dans la lecture du flux et par conséquent la boucle s'arrête. Et il se convertit à true si la lecture a réussi et que la boucle continue.


57
2018-04-09 12:59



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

Si vous utilisez la ligne 2 en 3 et la ligne 3 en 2, vous obtiendrez ch imprimé deux fois. Donc cout avant de lire.


-3
2018-03-10 19:30