ループ条件内のiostream :: eof(つまり、 `while(!stream.eof())`)が間違っていると見なされるのはなぜですか?

Apr 09 2011

この回答iostream::eofで、ループ条件での使用は「ほぼ間違いなく間違っている」というコメントを見つけました。私は通常、次のようなものを使用しますwhile(cin>>n)-暗黙的にEOFをチェックすると思います。

なぜeofを明示的にwhile (!cin.eof())間違って使用してチェックするのですか?

scanf("...",...)!=EOFCでの使用(私は問題なく使用することが多い)とどう違うのですか?

回答

556 Xeo Apr 09 2011 at 19:58

そのためiostream::eofにのみ返されますtrue 、ストリームの終わりを読みました。次の読み取りがストリームの終わりになることを示すものではありませ

これを考慮してください(そして、次の読み取りがストリームの最後になると仮定します):

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
}

これに対して:

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

そしてあなたの2番目の質問について:なぜなら

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

と同じです

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

と同じではありません

if(!inStream.eof())
    inFile >> data
104 sly Nov 24 2012 at 06:30

ボトムライントップ:空白を適切に処理すると、次のようeofに使用できます(さらに、fail()エラーチェックよりも信頼性が高くなります)。

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

答えを強調する提案をしてくれたTony Dに感謝します。これがより堅牢である理由の例については、以下の彼のコメントを参照してください。


使用に反対する主な議論eof()は、空白の役割についての重要な微妙さを欠いているようです。私の提案は、eof()明示的にチェックすることは「常に間違っている」だけでなく(これはこのスレッドや同様のSOスレッドで最も重要な意見のようです)、空白を適切に処理することで、よりクリーンで信頼性の高いものを提供するということです。エラー処理であり、常に正しい解決策です(ただし、必ずしも最も簡単であるとは限りません)。

「適切な」終了および読み取り順序として提案されているものを要約すると、次のようになります。

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

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

eofを超えた読み取り試行による失敗は、終了条件と見なされます。これは、成功したストリームとeof以外の理由で実際に失敗したストリームを区別する簡単な方法がないことを意味します。次のストリームを取ります。

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

while(in>>data)3つの入力すべてのセットfailbitで終了します。1回目と3回目も設定されています。したがって、ループを過ぎると、適切な入力(1番目)と不適切な入力(2番目と3番目)を区別するために、非常に醜い追加のロジックが必要になります。eofbit

一方、次のことを行ってください。

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

ここで、in.fail()読むものがある限り、それが正しいものであることを確認します。その目的は、単なるwhileループターミネーターではありません。

これまでのところ良好ですが、ストリームに後続のスペースがある場合はどうなりeof()ますか?ターミネーターとしての主な懸念事項のように聞こえますか?

エラー処理を放棄する必要はありません。空白を食い尽くすだけです:

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を設定している間、ストリーム内の潜在的な(ゼロ以上の)後続スペースをスキップします。。はスキップeofbitませんfailbit。したがって、in.fail()読み取るデータが少なくとも1つある限り、期待どおりに機能します。すべて空白のストリームも受け入れられる場合、正しい形式は次のとおりです。

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
}

概要:適切に構築されてwhile(!eof)いることは可能であり、間違いではないだけでなく、データをスコープ内にローカライズすることを可能にし、通常どおりビジネスからエラーチェックをより明確に分離します。そうは言っても、while(!fail)間違いなくより一般的で簡潔なイディオムであり、単純な(読み取りタイプごとに単一のデータ)シナリオで好まれる可能性があります。

75 Nawaz Apr 09 2011 at 19:59

プログラマーが書かない場合while(stream >> n)、彼らはおそらくこれを書くからです:

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

ここでの問題は、some work on nストリームの読み取りが成功したかどうかを最初に確認せずに行うことはできませんsome work on n。失敗した場合、望ましくない結果が生じるためです。

全体のポイントは、ということであるeofbitbadbitまたはfailbit設定されている試みがストリームから読み取るために作られた後。もしそうならstream >> n、その後、失敗したeofbitbadbitまたはfailbitすぐに設定されている、あなたが書いた場合は、その多くの慣用的なのでwhile (stream >> n)、返されたオブジェクトのためにstream変換するfalseいくつかの障害が流れ、その結果、ループ停止からの読み取りであった場合。そしてtrue、読み取りが成功してループが継続するかどうかに変換されます。

10 melpomene May 04 2019 at 16:52

他の回答は、ロジックが間違っている理由while (!stream.eof())とそれを修正する方法を説明しています。私は何か違うことに焦点を合わせたい:

eofを明示的にiostream::eof間違って使用しているのはなぜですか?

一般的に、ストリーム抽出()はファイルの終わりに到達せずに失敗する可能性があるため、チェックeof のみが間違っ>>ています。たとえばint n; cin >> n;、ストリームにが含まれているhello場合、hは有効な数字ではないため、入力の最後に到達せずに抽出は失敗します。

この問題は、ストリームの状態を読み取ろうとするにチェックするという一般的な論理エラーと相まって、N個の入力アイテムに対してループがN + 1回実行されることを意味し、次の症状を引き起こします。

  • ストリームが空の場合、ループは1回実行されます。>>失敗し(読み取る入力がない)、(によってstream >> x)設定されるはずだったすべての変数は実際には初期化されていません。これにより、ガベージデータが処理され、無意味な結果(多くの場合、膨大な数)として現れる可能性があります。

    (標準ライブラリがC ++ 11に準拠している場合、状況は少し異なります。失敗>>すると、数値変数が0初期化されないままになるのではなく、に設定されるようになりました(charsを除く)。)

  • ストリームが空でない場合、最後の有効な入力の後にループが再度実行されます。最後の反復ではすべての>>操作が失敗するため、変数は前の反復からの値を保持する可能性があります。これは、「最後の行が2回印刷される」または「最後の入力レコードが2回処理される」として現れる可能性があります。

    (これは、C ++ 11(上記を参照)以降、少し異なる形で現れるはずです。これで、最後の行が繰り返される代わりに、ゼロの「ファントムレコード」が得られます。)

  • ストリームに不正な形式のデータが含まれているが.eof、チェックするだけの場合、無限ループになります。>>ストリームからデータを抽出できないため、ループは最後に到達することなく所定の位置で回転します。


要約すると、解決策は>>、別の.eof()メソッドを使用するのではなく、操作自体while (stream >> n >> m) { ... }の成功をテストすることです。Cの場合と同様に、scanf呼び出し自体の成功をテストしますwhile (scanf("%d%d", &n, &m) == 2) { ... }