694 votes

Pourquoi iostream::eof à l'intérieur d'une condition de boucle (c'est-à-dire `while (!stream.eof())`) est-il considéré comme mauvais ?

Je viens de trouver un commentaire dans este réponse disant qu'en utilisant iostream::eof dans 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 la présence de EOF.

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

Quelle est la différence avec l'utilisation de scanf("...",...)!=EOF en C (que j'utilise souvent sans problème) ?

27 votes

scanf(...) != EOF ne fonctionnera pas non plus en C, car scanf renvoie le nombre de champs analysés et attribués avec succès. La condition correcte est scanf(...) < n donde n est le nombre de champs dans la chaîne de format.

8 votes

@Ben Voigt, il retournera un nombre négatif (que EOF est généralement défini comme tel) au cas où EOF est atteint

23 votes

@SebastianGodelet : En fait, il retournera EOF si 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 deux champs, il renverra le nombre de champs convertis et stockés avec succès. Ce qui rend la comparaison avec EOF mauvais.

617voto

Xeo Points 69818

Parce que iostream::eof ne retournera que true après en lisant la fin du flux. Il fait no indique 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 ça :

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 pour votre deuxième question : Parce que

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

est la même chose que

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

y no la même chose que

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

14 votes

Il convient de mentionner que if ( !(inStream >> data).eof()) ne fait rien d'utile non plus. Erreur 1 : Il n'entrera pas dans la condition s'il n'y a pas d'espace après la dernière donnée (la dernière donnée ne sera pas traitée). Erreur 2 : Elle 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 vieilles données encore et encore).

0 votes

Légèrement hors sujet mais laissez-moi le dire. Si l'on utilise l'évaluation paresseuse, cette approche réussirait-elle sans problème ?

5 votes

Je pense qu'il est utile de souligner que cette réponse est légèrement trompeuse. Lorsque l'on extrait int ou std::string ou similaire, le bit EOF es réglé lorsque vous extrayez celui qui se trouve 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 activé lors de la lecture d'un fichier est qu'il y a une ligne supplémentaire de type \n à la fin. J'ai abordé ce sujet dans une autre réponse . Lecture char s est une question différente car il n'extrait qu'un seul élément à la fois et ne continue pas jusqu'à la fin.

117voto

sly Points 600

Le top du top : En manipulant correctement les espaces blancs, voici comment procéder eof peuvent être utilisées (et même, être plus fiables que les 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. )


Le principal argument contre l'utilisation eof() semble manquer une subtilité importante sur le rôle de l'espace blanc. Ma proposition est que, en vérifiant eof() toujours faux " - ce qui semble être l'opinion dominante dans ce fil de discussion et dans d'autres fils de discussion similaires sur les SO -, mais avec une gestion correcte des espaces blancs, il permet une gestion des erreurs plus propre et plus fiable, et il est l'outil de gestion des erreurs le plus efficace. toujours correct (bien qu'il ne s'agisse pas nécessairement de la solution la plus efficace).

En résumé, ce qui est suggéré comme étant l'ordre "correct" de terminaison et 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 d'arrêt. Cela signifie qu'il n'y a pas de moyen facile de distinguer entre un flux réussi et un flux qui échoue réellement pour des raisons autres que eof. Prenons 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 failbit para tous trois entrées. Dans le premier et le troisième, eofbit est également fixé. Ainsi, au-delà de la boucle, il faut une logique supplémentaire très moche pour distinguer une entrée correcte (1ère) d'une entrée incorrecte (2ème et 3ème).

Attendu que, prenez 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 queue dans le flux - ce qui semble être le problème majeur contre eof() comme Terminator ?

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::ws saute tout espace de fin de ligne potentiel (zéro ou plus) dans le flux, tandis que le réglage de l'option eofbit y pas le failbit . Donc, in.fail() fonctionne comme prévu, tant qu'il y a au moins une donnée à lire. Si les flux entièrement vides sont également acceptables, alors 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) n'est pas seulement possible et n'est pas mauvaise, mais elle permet de localiser les données dans la portée et de séparer plus proprement la vérification des erreurs de l'activité habituelle. Ceci étant dit, while(!fail) est incontestablement un idiome plus courant et plus concis, et peut être préféré dans des scénarios simples (du type données uniques par lecture).

8 votes

" Ainsi, au-delà de la boucle, il n'y a pas de moyen (facile) de distinguer une entrée correcte d'une entrée incorrecte. " Sauf que dans un cas, les deux eofbit y failbit sont fixés, dans l'autre seulement failbit est fixé. Vous devez seulement tester que une fois après la fin de la boucle, et non pas à chaque itération ; il ne quittera la boucle qu'une seule fois, donc vous n'avez besoin de vérifier que pourquoi il a quitté la boucle une fois. while (in >> data) fonctionne bien pour tous les flux vierges.

3 votes

Ce que vous dites (et un point soulevé plus tôt) est qu'un flux mal formaté peut être identifié en tant que !eof & fail boucle passée. Il y a des cas où l'on ne peut pas compter sur cela. Voir le commentaire ci-dessus ( goo.gl/9mXYX ). Quoi qu'il en soit, je ne propose pas eof -Vérifier en tant que le-toujours-meilleur alternatif. Je dis simplement que es une manière possible et (dans certains cas plus appropriée) de procéder, plutôt que "très certainement erronée" comme on a tendance à le prétendre ici dans l'OS.

2 votes

"A titre d'exemple, considérez comment vous vérifieriez les erreurs lorsque les données sont une structure avec un opérateur surchargé>> lisant plusieurs champs à la fois" - un cas beaucoup plus simple soutenant votre point de vue est stream >> my_int où le flux contient par exemple "-" : eofbit y failbit sont fixés. C'est pire que le operator>> scénario dans lequel la surcharge fournie par l'utilisateur a au moins la possibilité d'effacer eofbit avant de revenir pour aider à soutenir while (s >> x) usage. De manière plus générale, cette réponse aurait besoin d'un nettoyage - seul le dernier while( !(in>>ws).eof() ) est généralement robuste, et il est enterré à la fin.

79voto

Nawaz Points 148870

Parce que si les programmeurs n'écrivent pas while(stream >> n) ils ont pu écrire ça :

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

Le problème, c'est que vous ne pouvez pas faire some work on n sans vérifier d'abord si la lecture du flux a réussi, car si elle a échoué, votre some work on n produirait un résultat indésirable.

Le but est que, eofbit , badbit ou failbit sont fixés après une tentative de lecture du flux. Donc si stream >> n échoue, alors eofbit , badbit ou failbit est défini immédiatement, il est donc plus idiomatique d'écrire while (stream >> n) car l'objet retourné stream se 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 en true si la lecture a réussi et la boucle continue.

2 votes

Outre le "résultat indésirable" mentionné dans le cas d'un travail sur une valeur non définie de n le programme peut également tomber dans un boucle infinie si l'opération de streaming défaillante ne consomme pas d'entrée.

17voto

melpomene Points 5675

Les autres réponses ont expliqué pourquoi la logique est fausse en while (!stream.eof()) et comment le réparer. Je veux me concentrer sur quelque chose de différent :

pourquoi la vérification de l'e pour explic explic explic explic de l'utiliser iostream::eof mal ?

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

Ce problème, associé à l'erreur de logique générale consistant à vérifier l'état du flux avant Si l'on tente d'y lire, ce qui signifie que pour N éléments d'entrée, la boucle s'exécutera N+1 fois, on obtient les symptômes suivants :

  • Si le flux est vide, la boucle sera exécutée une fois. >> échouera (il n'y a pas d'entrée à lire) et toutes les variables qui étaient censées être définies (par la fonction stream >> x ) sont en fait non initialisés. Cela entraîne le traitement de 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 >> fixe désormais les variables numériques à 0 au lieu de les laisser non initialisés (à l'exception de char s).)

  • Si le flux n'est pas vide, la boucle s'exécutera à nouveau après la dernière entrée valide. Puisque lors de la dernière itération, tous les >> échouent, les variables sont susceptibles de conserver leur valeur de 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 malformées mais que vous vérifiez seulement si .eof vous vous retrouvez avec une boucle infinie. >> ne parviendra pas à extraire de données du flux, de sorte que la boucle tourne en place sans jamais atteindre la fin.


Pour récapituler : La solution consiste à tester le succès de la >> l'opération elle-même, et non d'utiliser une .eof() méthode : while (stream >> n >> m) { ... } tout comme en C, vous testez le succès de l'exécution de la commande scanf l'appel lui-même : while (scanf("%d%d", &n, &m) == 2) { ... } .

1 votes

C'est la réponse la plus précise, bien qu'à partir de c++11, je ne crois plus que les variables soient non initialisées (le premier point).

5voto

Ce qu'il faut retenir, c'est que, inFile.eof() ne devient pas True jusqu'à après une tentative de lecture échoue, car vous avez atteint la fin du fichier. Donc, dans cet exemple, vous obtiendrez une erreur.

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

Pour que cette boucle soit correcte, il faut combiner la lecture et la vérification en une seule opération, comme suit

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

Par convention, operator>> renvoie le flux à partir duquel nous avons lu, et un test booléen sur un flux renvoie False lorsque le flux échoue (par exemple, lorsqu'il atteint la fin du fichier).

Cela nous donne donc la séquence correcte :

  • lire
  • tester si la lecture réussit
  • si et seulement si le test réussit, traiter ce que nous avons lu.

Si vous rencontrez des autre problème qui vous empêche de lire le fichier correctement, vous ne pourrez pas atteindre eof() comme tel. Par exemple, regardons quelque chose comme ceci

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

Retraçons le fonctionnement du code ci-dessus, à l'aide d'un exemple.

  • Supposons que le contenu du fichier soit '1', '2', '3', 'a', 'b' .
  • La boucle lira correctement les 1, 2 et 3.
  • Ensuite, il s'agira de a .
  • Quand il essaie d'extraire a comme un int, ça échouera.
  • Le flux est maintenant dans un état d'échec, jusqu'à ce que ou à moins que nous clear le flux, toutes les tentatives de lecture de celui-ci échoueront.
  • Mais, quand nous testons eof(), il retournera False parce qu'on n'est pas à la fin du fichier, parce qu'il y a toujours a qui attendent d'être lus.
  • La boucle continuera d'essayer de lire le fichier, et échouera à chaque fois, donc elle jamais atteint la fin du fichier.
  • Ainsi, la boucle ci-dessus sera exécutée à l'infini.

Mais, si nous utilisons une boucle comme celle-ci, nous obtiendrons la sortie requise.

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

Dans ce cas, le flux sera converti en False non seulement en cas de fin de fichier, mais aussi en cas d'échec de la conversion, comme le fichier a qu'on ne peut pas lire comme un nombre entier.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X