web-dev-qa-db-fra.com

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

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

Pourquoi la vérification de eof utilisant explicitement while (!cin.eof()) est-elle incorrecte?

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

557
MAK

Parce que iostream::eof ne renverra que true après en lisant 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 trouve à 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 ceci:

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 le même que

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

Bottom-line top: Avec un traitement correct des espaces, voici comment eof peut être utilisé (et même être plus fiable que fail() pour la vérification d'erreur):

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 la raison pour laquelle c'est plus robuste. )


L'argument principal contre l'utilisation de eof() semble manquer d'une subtilité importante sur le rôle de l'espace blanc. Ma proposition est que, vérifier eof() explicitement n’est pas non plus toujours " toujours faux " - ce qui semble être une opinion prépondérante dans ce domaine et similaires SO threads -, mais avec une gestion correcte des espaces blancs, il fournit une gestion des erreurs plus propre et plus fiable, et est le toujours correct solution (bien que, pas nécessairement le plus bas).

Pour résumer ce qui est suggéré comme étant le "bon" arrêt et ordre de lecture, voici ce qui suit:

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 pris comme condition de terminaison. Cela signifie qu'il n'y a pas de moyen facile de faire la distinction entre un flux réussi et un flux qui échoue réellement 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 failbit pour toutes les trois entrées. Dans la première et la troisième, eofbit est également défini. Donc, au-delà de la boucle, il faut une logique supplémentaire très laide pour distinguer une entrée correcte (1ère) de celle incorrecte (2ème et 3ème).

Considérant 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 fin dans le flux - qu'est-ce qui semble être la préoccupation majeure contre eof() en tant que terminateur?

Nous n'avons pas besoin d'abandonner notre traitement 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 lors du réglage de eofbit, et et non de failbit. Ainsi, in.fail() fonctionne comme prévu, tant qu’il reste au moins une donnée à lire. Si les flux entièrement vierges sont également acceptables, le formulaire correct est le suivant:

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 while(!eof) correctement construite est non seulement possible et non fausse, mais elle permet également de localiser les données dans le périmètre, et offre une séparation plus nette de la vérification des erreurs des activités habituelles. Cela étant dit, while(!fail) est sans conteste un idiome plus courant et plus complexe, et peut être préféré dans des scénarios simples (données uniques par type de lecture).

98
sly

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 que vous ne pouvez pas faire some work on n sans d'abord vérifier si le flux de lecture a réussi, car s'il échouait, votre some work on n produirait un résultat non souhaité.

Le problème est que eofbit, badbit ou failbit sont définis après une tentative de lecture dans le flux. Donc, si stream >> n échoue, alors eofbit, badbit ou failbit est défini immédiatement; il est donc plus idiomatique si vous écrivez while (stream >> n), car l'objet renvoyé stream est converti en false en cas d'échec de la lecture dans le flux et, par conséquent, de la boucle. Et il se convertit en true si la lecture a réussi et que la boucle continue.

68
Nawaz

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

pourquoi la vérification de eof utilisant explicitement iostream::eof est-elle fausse?

En termes généraux, la vérification de eofniquement est erronée car l'extraction de flux (>>) peut échouer sans toucher à la fin du fichier. Si vous avez par exemple int n; cin >> n; et le flux contient hello, alors h n'est pas un chiffre valide. L'extraction échouera sans atteindre la fin de l'entrée.

Ce problème, associé à l'erreur de logique générale liée à la vérification de l'état du flux avant essayer de le lire, ce qui signifie que pour la N entrées, la boucle s'exécutera N + 1 fois, entraîne les symptômes suivants:

  • Si le flux est vide, la boucle s'exécutera une fois. >> échouera (aucune entrée ne sera lue) et toutes les variables supposées être définies (par stream >> x) sont en réalité non initialisées. Cela entraîne le traitement de données non conformes, ce qui peut se traduire 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 >> échoué définit désormais les variables numériques sur 0 au lieu de les laisser non initialisées (sauf pour chars) .)

  • Si le flux n'est pas vide, la boucle sera exécutée après la dernière entrée valide. Comme lors de la dernière itération, toutes les opérations >> échouent, il est probable que les variables conservent leur valeur depuis 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 vérifiez que .eof, vous vous retrouvez avec une boucle infinie. >> ne parviendra pas à extraire les données du flux, de sorte que la boucle tourne sur place sans jamais atteindre la fin.


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

3
melpomene