Pourquoi iostream :: eof dans une condition de boucle (c'est-à-dire `while (! Stream.eof ())`) est-il considéré comme incorrect?

595

Je viens de trouver un commentaire dans cette réponse disant que l'utilisation iostream::eofdans une condition de boucle est "presque certainement fausse". J'utilise généralement quelque chose comme while(cin>>n)- qui, je suppose, vérifie implicitement EOF.

Pourquoi la vérification de eof utilise- while (!cin.eof())t-elle explicitement mal?

En quoi est-ce différent de l'utilisation scanf("...",...)!=EOFen C (que j'utilise souvent sans problème)?

MAK
la source
21
scanf(...) != EOFne fonctionnera pas non plus en C, car scanfrenvoie le nombre de champs correctement analysés et attribués. La condition correcte est scanf(...) < nnest le nombre de champs dans la chaîne de format.
Ben Voigt
5
@Ben Voigt, il renverra un nombre négatif (que EOF est généralement défini comme tel) au cas où EOF serait atteint
Sebastian
19
@SebastianGodelet: En fait, il retournera EOFsi la fin du fichier est rencontrée avant la première conversion de champ (réussie ou non). Si la fin du fichier est atteinte entre les champs, il retournera le nombre de champs convertis et stockés avec succès. Ce qui rend la comparaison EOFerronée.
Ben Voigt
1
@SebastianGodelet: Non, pas vraiment. Il se trompe quand il dit que "après la boucle, il n'y a pas de moyen (facile) de distinguer une entrée correcte d'une entrée incorrecte". En fait, c'est aussi simple que de vérifier .eof()après la sortie de la boucle.
Ben Voigt
2
@Ben Oui, pour ce cas (lecture d'un simple int). Mais on peut facilement trouver un scénario où la while(fail)boucle se termine avec à la fois une défaillance réelle et un eof. Réfléchissez si vous avez besoin de 3 ints par itération (disons que vous lisez un point xyz ou quelque chose), mais qu'il n'y a, à tort, que deux ints dans le flux.
rusé

Réponses:

544

Parce iostream::eofque ne reviendra true qu'après avoir lu la fin du flux. Cela n'indique pas que la prochaine lecture sera la fin du flux.

Considérez ceci (et supposez que la prochaine lecture se fera à 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 pas la même chose que

if(!inStream.eof())
    inFile >> data
Xeo
la source
12
Il convient de mentionner que si (! (InStream >> data) .eof ()) ne fait rien d'utile non plus. Erreur 1: Il n'entrera pas dans la condition s'il n'y avait pas d'espace blanc après la dernière donnée (la dernière donnée ne sera pas traitée). Erreur 2: Il entrera dans la condition même si la lecture des données a échoué, tant que EOF n'a pas été atteint (boucle infinie, traitant les mêmes anciennes données encore et encore).
Tronic
4
Je pense qu'il convient de souligner que cette réponse est légèrement trompeuse. Lors de l'extraction de ints ou std::strings ou similaire, le bit EOF est défini lorsque vous extrayez celui juste avant la fin et que l'extraction atteint la fin. Vous n'avez pas besoin de relire. La raison pour laquelle il n'est pas défini lors de la lecture à partir de fichiers est qu'il y a un supplément \nà la fin. J'ai couvert cela dans une autre réponse . La lecture de chars est une question différente car elle n'en extrait qu'un à la fois et ne continue pas à la fin.
Joseph Mansfield
79
Le principal problème est que ce n'est pas parce que nous n'avons pas atteint l'EOF que la prochaine lecture réussira .
Joseph Mansfield
1
@sftrabbit: tout est vrai mais pas très utile ... même s'il n'y a pas de "\ n" de fin, il est raisonnable de vouloir que les autres espaces de fin soient traités de manière cohérente avec les autres espaces dans le fichier (c'est-à-dire ignorés). En outre, une conséquence subtile de "lorsque vous extrayez celui juste avant" est que while (!eof())cela ne "fonctionnera" pas sur ints ou std::strings lorsque l'entrée est totalement vide, donc même en sachant qu'il n'y a pas de \nsoin de fin est nécessaire.
Tony Delroy
2
@TonyD Tout à fait d'accord. La raison pour laquelle je dis cela est parce que je pense que la plupart des gens, lorsqu'ils liront ceci et des réponses similaires, penseront que si le flux contient "Hello"(aucun espace de fin ou \n) et que a std::stringest extrait, il extraira les lettres de Hà o, arrêtera l'extraction et alors ne définissez pas le bit EOF. En fait, cela réglerait le bit EOF car c'est l'EOF qui a arrêté l'extraction. Espérant simplement clarifier cela pour les gens.
Joseph Mansfield
103

Bottom-line top: Avec une gestion correcte de l'espace blanc, voici comment eofpeut ê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 de pourquoi il est plus robuste. )


Le principal argument contre l'utilisation eof()semble manquer d'une subtilité importante sur le rôle de l'espace blanc. Ma proposition est que, la vérification eof()explicite n'est pas seulement " toujours erronée " - ce qui semble être une opinion prépondérante dans ce filetage SO et similaire -, mais avec une gestion appropriée de l'espace blanc, elle fournit un filtre plus propre et plus fiable. gestion des erreurs, et est la solution toujours correcte (bien que, pas nécessairement le plus tersest).

Pour résumer ce qui est suggéré comme la terminaison «correcte» et l'ordre de lecture est le suivant:

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

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

L'échec dû à une tentative de lecture au-delà de eof est considéré comme la condition de résiliation. Cela signifie qu'il n'y a pas de moyen simple de distinguer un flux réussi d'un flux qui échoue vraiment pour des raisons autres que 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 par un ensemble failbitpour les trois entrées. Dans le premier et le troisième, eofbitest également défini. Donc, au-delà de la boucle, il faut une logique supplémentaire très moche pour distinguer une entrée correcte (1ère) des entrées incorrectes (2e et 3e).

Attendez-vous à ce qui suit:

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 de boucle while.

Jusqu'ici tout va bien, mais que se passe-t-il s'il y a un espace de fuite dans le flux - ce qui semble être la principale préoccupation contre en eof()tant que terminateur?

Nous n'avons pas besoin de renoncer à notre gestion des erreurs; il suffit de manger 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::wsignore tout espace de fuite potentiel (zéro ou plus) dans le flux lors de la définition de eofbit, et non defailbit . Donc, in.fail()fonctionne comme prévu, tant qu'il y a au moins une donnée à lire. Si des flux entièrement vierges sont également acceptables, la forme correcte est:

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é: Une construction correcte while(!eof)est non seulement possible et non fausse, mais elle permet de localiser les données dans la portée et fournit une séparation plus nette entre la vérification des erreurs et le fonctionnement normal. Cela étant dit, while(!fail)est incontestablement un idiome plus courant et plus laconique, et peut être préféré dans des scénarios simples (données uniques par type de lecture).

sournois
la source
6
« Donc , après la boucle il n'y a pas (facile) moyen de distinguer une entrée correcte d'un un mauvais. » Sauf que dans un cas , à la fois eofbitet failbitsont fixés, dans l'autre failbitn'est défini. Vous n'avez besoin de tester qu'une seule fois après la fin de la boucle, pas à chaque itération; il ne quittera la boucle qu'une seule fois, il vous suffit donc de vérifier pourquoi il a quitté la boucle une fois. while (in >> data)fonctionne bien pour tous les flux vides.
Jonathan Wakely
3
Ce que vous dites (et un point soulevé plus tôt) est qu'un mauvais flux formaté peut être identifié comme !eof & failune boucle passée. Il y a des cas où l'on ne peut pas se fier à cela. Voir le commentaire ci-dessus ( goo.gl/9mXYX ). Quoi qu'il en soit, je ne propose pas eof-check comme l' alternative toujours meilleure . Je dis simplement, il est un moyen possible et (dans certains cas , plus approprié) de le faire, plutôt que « certainement tort! » car il a tendance à être revendiqué ici dans SO.
sournoise
2
"Par exemple, réfléchissez à la façon dont vous vérifieriez les erreurs dans lesquelles les données sont une structure avec un opérateur surchargé >> en lisant plusieurs champs à la fois" - un cas beaucoup plus simple prenant en charge votre point est celui stream >> my_intoù le flux contient par exemple "-": eofbitet failbitsont ensemble. C'est pire que le operator>>scénario, où la surcharge fournie par l'utilisateur a au moins la possibilité de s'effacer eofbitavant de revenir pour aider à prendre en charge l' while (s >> x)utilisation. Plus généralement, cette réponse pourrait utiliser un nettoyage - seule la finale while( !(in>>ws).eof() )est généralement robuste, et elle est enterrée à la fin.
Tony Delroy
74

Parce que si les programmeurs n'écrivent pas while(stream >> n), ils écrivent éventuellement ceci:

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

Ici, le problème est que vous ne pouvez pas vous some work on npasser de vérifier d'abord si la lecture du flux a réussi, car si elle échouait, some work on ncela produirait un résultat indésirable.

Le tout est que eofbit, badbitou failbitsont mis après une tentative de lecture du flux. Donc, si stream >> néchoue, alors eofbit, badbitou failbitest défini immédiatement, donc c'est plus idiomatique si vous écrivez while (stream >> n), car l'objet retourné se streamconvertit en falsecas d'échec de lecture dans le flux et, par conséquent, la boucle s'arrête. Et il se transforme en truesi la lecture a réussi et la boucle continue.

Nawaz
la source
1
Mis à part le "résultat indésirable" mentionné avec le travail sur la valeur indéfinie de n, le programme peut également tomber dans une boucle infinie , si l'opération de flux en échec ne consomme aucune entrée.
mastov
10

Les autres réponses ont expliqué pourquoi la logique est erronée while (!stream.eof())et comment y remédier. Je veux me concentrer sur quelque chose de différent:

pourquoi la vérification de eof utilise- iostream::eoft-elle explicitement mal?

De manière générale, la vérification de eof uniquement est incorrecte car l'extraction de flux ( >>) peut échouer sans atteindre la fin du fichier. Si vous avez par exemple int n; cin >> n;et que le flux contient hello, alors ce hn'est pas un chiffre valide, donc l'extraction échouera sans atteindre la fin de l'entrée.

Ce problème, combiné à l'erreur logique générale de vérification de l'état du flux avant d' essayer de le lire, ce qui signifie que pour N éléments d'entrée, la boucle s'exécutera N + 1 fois, conduit aux symptômes suivants:

  • Si le flux est vide, la boucle s'exécutera une fois. >>échouera (il n'y a pas d'entrée à lire) et toutes les variables qui devaient être définies (par stream >> x) ne sont en fait pas initialisées. Cela entraîne le traitement des données inutiles, qui peuvent se manifester par des résultats absurdes (souvent des nombres énormes).

    (Si votre bibliothèque standard est conforme à C ++ 11, les choses sont un peu différentes maintenant: un échec >>définit désormais les variables numériques au 0lieu de les laisser non initialisées (sauf pour chars).)

  • Si le flux n'est pas vide, la boucle sera exécutée à nouveau après la dernière entrée valide. Étant donné que dans la dernière itération, toutes les >>opérations échouent, les variables sont susceptibles de conserver leur valeur par rapport à l'itération précédente. Cela peut se manifester par «la dernière ligne est imprimée deux fois» ou «le dernier enregistrement d'entrée est traité deux fois».

    (Cela devrait se manifester un peu différemment depuis C ++ 11 (voir ci-dessus): vous obtenez maintenant un "enregistrement fantôme" de zéros au lieu d'une dernière ligne répétée.)

  • Si le flux contient des données mal formées mais que vous ne faites que vérifier .eof, vous vous retrouvez avec une boucle infinie. >>ne parviendra pas à extraire les données du flux, de sorte que la boucle tourne en place sans jamais atteindre la fin.


Pour récapituler: La solution est de tester le succès de l' >>opération elle - même, de ne pas utiliser une distincte .eof()méthode: while (stream >> n >> m) { ... }, comme en C tester le succès de l' scanfappel lui - même: while (scanf("%d%d", &n, &m) == 2) { ... }.

melpomene
la source
1
c'est la réponse la plus précise, bien qu'à partir de c ++ 11, je ne crois pas que les variables ne soient plus initialisées (la première puce pt)
csguy