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


595

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

eofのチェックが明示的にwhile (!cin.eof())間違っているのはなぜですか?

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


21
scanf(...) != EOFCでも機能しませんscanf。正常に解析および割り当てられたフィールドの数を返すためです。正しい条件は、scanf(...) < nここでnフォーマット文字列内のフィールドの数です。
Ben Voigt

5
@Ben Voigt、EOFに達した場合に負の数(通常、EOFはそのように定義されています)を返します
Sebastian

19
@SebastianGodelet:実際EOFには、最初のフィールド変換の前にファイルの終わりに達した場合(成功したかどうかにかかわらず)戻ります。フィールド間でファイルの終わりに達した場合、変換と保存が成功したフィールドの数が返されます。これはEOF間違っているとの比較になります。
Ben Voigt

1
@SebastianGodelet:いいえ、そうでもありません。「過去のループには、適切な入力と不適切な入力を区別する(簡単な)方法はない」と彼が誤解している。実際.eof()、ループが終了した後のチェックと同じくらい簡単です。
Ben Voigt

2
@Benはい、この場合(単純なintを読み取る)。しかしwhile(fail)、実際の障害とEOFの両方でループが終了するシナリオは簡単に思いつくことがあります。反復ごとに3つの整数が必要かどうか(xyzポイントなどを読み取る場合など)を考えてみてください。ただし、誤ってストリームに2つの整数しかありません。
slyの

回答:


544

そのため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

12
言及する価値があるのは、もし(!(inStream >> data).eof())も何も役に立たないということです。誤解1:データの最後の部分の後に空白がなかった場合、条件は入力されません(最後のデータは処理されません)。誤解2:データの読み取りが失敗した場合でも、EOFに到達しない限り(無限ループ、同じ古いデータを何度も処理する)、この状態になります。
Tronic、

4
この答えは少し誤解を招くものであることを指摘する価値があると思います。抽出する際にintSまたはstd::stringSまたは同様に、EOFビットがされますが、終了前に1権利を抽出したときに設定し、抽出が終わりに当たります。もう一度読む必要はありません。ファイルから読み取るときに設定されない理由\nは、最後に余分があるためです。これについては別の回答で説明しましたcharsの読み取りは、1度に1つしか抽出せず、最後までヒットしないため、別の問題です。
ジョセフマンスフィールド

79
主な問題は、EOFに達していないからといって、次の読み取りが成功するということではありません
ジョセフマンスフィールド

1
@sftrabbit:すべて真ですがあまり有用ではありません...末尾の「\ n」がない場合でも、他の末尾の空白をファイル全体で他の空白と一貫して処理する(つまり、スキップする)のが妥当です。さらに、「直前のものを抽出するとき」の微妙な結果は、入力が完全に空の場合while (!eof())intsまたはstd::stringsで「機能しない」こと\nです。そのため、後続のケアがないことを知っていても必要ありません。
トニーDelroy 2013

2
@TonyD完全に同意します。これを言っている理由は、ほとんどの人がこれと同様の答えを読んだときに、ストリームに"Hello"(末尾の空白またはがない\n)とa std::stringが抽出された場合、それがHto から文字をo抽出し、抽出を停止し、そして次に、EOFビットを設定しません。実際、抽出を停止したのはEOFであったため、EOFビットを設定します。人々のためにそれを片付けたいと思っています。
ジョセフマンスフィールド

103

結論: 空白を適切に処理すると、次のよう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で終了します。最初と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-loopターミネーターではありません。

これまでのところ良いですが、ストリームに末尾のスペースがある場合はどうなり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)間違いなくより一般的で簡潔なイディオムであり、単純な(読み取りタイプごとに単一のデータ)シナリオで好まれます。


6
だから、ループを過ぎて不適切なものから適切な入力を区別する(簡単な)方法はありません。」ことを除いて一つのケースの両方eofbitfailbit設定されている、他にのみfailbit設定されています。テストが必要なのは、ループが終了し後で1回だけで、すべての反復ではありません。ループから抜けるのは1回だけなので、ループを一度抜けた理由を確認するだけで済みます。 while (in >> data)すべての空のストリームで正常に動作します。
Jonathan Wakely 2013

3
あなたが言っていること(そして以前に述べたポイント)は、不正な形式のストリームが!eof & fail過去のループとして識別される可能性があるということです。これに頼ることができない場合があります。上記のコメントを参照してください(goo.gl/9mXYX)。いずれにせよ、私は常により良い代替手段eofとして-checkを提案していません。私が言っているの、「ほとんど間違いなく」これを行う方法ではなく、可能で(場合によってはより適切な)これを行う方法です。それはここSOで主張される傾向があるので。
2013

2
「一例として、一度に複数のフィールドを読ん>>データが過負荷演算子を持つ構造体であるエラーをチェックしたい方法を検討し、」はるかに簡単なケースあなたのポイントをサポートしているがある- stream >> my_intストリームなどが含まれている「 - 」:eofbitfailbitありますセットする。これoperator>>は、ユーザーが指定したオーバーロードが、使用eofbitをサポートするために戻る前に少なくともクリアするオプションを持っているシナリオよりも悪いですwhile (s >> x)。より一般的には、この答えはクリーンアップを使用できます-ファイナルのみwhile( !(in>>ws).eof() )が一般的に堅牢で、最後に埋め込まれます。
Tony Delroy、2015

74

プログラマーがを書かない場合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、読み取りが成功したかどうかに変換され、ループが継続します。


1
の未定義の値を処理することによる前述の「望ましくない結果」とは別に、失敗したストリーム操作が入力を消費しない場合、nプログラムは無限ループに陥ることもあります。
mastov

10

他の答えは、論理が間違っている理由while (!stream.eof())とそれを修正する方法を説明しています。別のことに焦点を当てたい:

なぜ明示的にiostream::eof間違って使用してeofをチェックするのですか?

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

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

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

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

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

    (これは、C ++ 11以降(上記を参照)とは少し異なるように見えるはずです。最後の行が繰り返されるのではなく、ゼロの「ファントムレコード」が表示されます。)

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


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


1
これは最も正確な答えですが、c ++ 11以降、変数が初期化されていないと
思い
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.