web-dev-qa-db-ja.com

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

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

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

Cでscanf("...",...)!=EOFを使用することとはどう違いますか(私はよく問題なく使用します)。

546
MAK

iostream::eofは、ストリームの最後を読み込んだときにtrueafterのみを返すためです。 notは、次の読み取りがストリームの終わりであることを示します。

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

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

notと同じ

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

一番下の行:空白を適切に処理することで、以下は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()を明示的にチェックすることは、「always wrong」だけではなく、これと同様の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つの入力のallセットのfailbitで終了します。最初と3番目では、eofbitも設定されます。したがって、ループを通過するには、適切な入力(1番目)と不適切な入力(2番目および3番目)を区別するための非常にい余分なロジックが必要です。

一方、次のとおりです。

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

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)は間違いなくより一般的で簡潔なイディオムであり、単純な(読み取りタイプごとの単一データ)シナリオで優先される場合があります。

98
sly

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

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

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

全体のポイントは、eofbitname __、badbitname__、またはfailbitname__が、ストリームからの読み取りが試行された後に設定されることです。したがって、stream >> nが失敗すると、eofbitbadbitname__、またはfailbitname__はすぐに設定されるため、while (stream >> n)を記述した場合はより慣用的です。返されるオブジェクトstreamname__は、ストリームからの読み取りに失敗してループが停止した場合にfalsename__に変換されるためですそして、読み取りが成功してループが継続する場合は、truename__に変換されます。

66
Nawaz

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

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

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

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

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

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

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


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

1
melpomene