9

次のプログラムは、std::istream (具体的には私のテスト コードでは std::istringstream) が eof() を設定する方法の矛盾を示しています。

#include <sstream>
#include <cassert>

int main(int argc, const char * argv[])
{
    // EXHIBIT A:
    {
        // An empty stream doesn't recognize that it's empty...
        std::istringstream stream( "" );
        assert( !stream.eof() );        // (Not yet EOF. Maybe should be.)
        // ...until I read from it:
        const int c = stream.get();
        assert( c < 0 );                // (We received garbage.)
        assert( stream.eof() );         // (Now we're EOF.)
    }
    // THE MORAL: EOF only happens when actually attempting to read PAST the end of the stream.

    // EXHIBIT B:
    {
        // A stream that still has data beyond the current read position...
        std::istringstream stream( "c" );
        assert( !stream.eof() );        // (Clearly not yet EOF.)
        // ... clearly isn't eof(). But when I read the last character...
        const int c = stream.get();
        assert( c == 'c' );             // (We received something legit.)
        assert( !stream.eof() );        // (But we're already EOF?! THIS ASSERT FAILS.)
    }
    // THE MORAL: EOF happens when reading the character BEFORE the end of the stream.

    // Conclusion: MADNESS.
    return 0;
}

そのため、実際のファイルの終わりのに文字を読み取ると、 eof() が「起動」します。ただし、ストリームが空の場合、実際に文字を読み取ろうとしたときにのみ発生します。eof() は「最後まで読み込もうとした」という意味ですか? または「もう一度読もうとすると、最後から外れますか?」答えは矛盾しています。

さらに、アサートが発火するかどうかはコンパイラに依存します。たとえば、Apple Clang 4.1 はアサーションを起動します (前の文字を読み取るときに eof() を発生させます)。たとえば、GCC 4.7.2 はそうではありません。

この矛盾により、ストリームを読み通すが、空のストリームと空でないストリームの両方を適切に処理する賢明なループを作成することが困難になります。

オプション1:

while( stream && !stream.eof() )
{
    const int c = stream.get();    // BUG: Wrong if stream was empty before the loop.
    // ...
}

オプション 2:

while( stream )
{
    const int c = stream.get();
    if( stream.eof() )
    {
        // BUG: Wrong when c in fact got the last character of the stream.
        break;
    }
    // ...
}

では、ストリームを解析し、各文字を順番に処理し、すべての文字を処理するループを作成するにはどうすればよいでしょうか。しかし、EOF に到達したとき、または最初からストリームが空の場合に大騒ぎせずに停止します。 、始まらない?

わかりました、より深い質問: peek() を使用すると、この eof() の不一致を何らかの方法で回避できるかもしれないという直感がありますが、...なんてこった! 矛盾の理由は?

4

5 に答える 5

9

このフラグは、操作eof()にファイルの終わりに到達したかどうかを判断する場合にのみ役立ちます。主な用途は、それ以上読み取るものがなく、読み取りが合理的に失敗した場合のエラー メッセージを回避することです。を使用してループまたは何かを制御しようとすると、必ず失敗します。いずれの場合も、読み取りが成功したかどうかを読み取ろうとしたに確認する必要があります。試みの前に、ストリームはあなたが何を読もうとしているのかを知ることができません。eof()

のセマンティクスはeof()、「このフラグは、ストリームの読み取りによってストリーム バッファーがエラーを返したときに設定される」と完全に定義されています。私が正しいと思い出すと、このステートメントを見つけるのはそれほど簡単ではありませんが、これが結果です。eof()ある時点で、標準では、必ずしも予期しない場合に設定される可能性がある状況で、ストリームが必要以上に読み取ることが許可されているとも述べています。そのような例の 1 つが文字の読み取りです。ストリームは、その文字と set の後に何もないことを検出してしまう可能性がありますeof()

空のストリームを処理したい場合は簡単です: ストリームから何かを見て、それが空でないことがわかっている場合にのみ続行します:

if (stream.peek() != std::char_traits<char>::eof()) {
    do_what_needs_to_be_done_for_a_non_empty_stream();
}
else {
    do_something_else();
}
于 2012-11-02T23:29:45.447 に答える
5

決して、eof一人でチェックすることはありません。

フラグ (によって返される値のビット フラグとeof同じ) は、抽出操作中にファイルの終わりに達すると設定されます。抽出操作がなかった場合、は設定されないため、最初のチェックで が返されます。eofbitrdstate()eofbitfalse

ただしeofbit、操作が成功したかどうかは示されません。そのためには、チェックインfailbit|badbitしてくださいrdstate()failbitは「論理エラーが発生しました」をbadbit意味し、「I/O エラーが発生しました」を意味します。便利なことに、fail()正確に を返す関数がありますrdstate() & (failbit|badbit)。さらに便利operator bool()なことに、 を返す関数があります!fail()。したがって、次のようなことができますwhile(stream.read(buffer)){ ...

操作が失敗した場合は、eofbit、 、badbitおよび をfailbit個別にチェックして、失敗した理由を突き止めることができます。

于 2012-11-03T00:02:53.077 に答える
1

どのコンパイラ/標準 C++ ライブラリを使用していますか? gcc 4.6.3/4.7.2 と clang 3.1 で試してみましたが、すべて問題なく動作しました (つまり、アサーションが起動しません)。

get() が文字を返すことができる限り、 eof() を設定すべきではないというあなたの直感と、標準の私の読書が一致するため、これをツールチェーンのバグとして報告する必要があると思います。

于 2012-11-02T23:37:56.223 に答える
1

意図した動作であるという意味では、これはバグではありません。入力が失敗するまでテストを使用する意図はあり ません。eof()主な目的は、抽出関数内で使用することです。初期の実装では、返されたという事実std::streambuf::sgetc()EOF、次に呼び出されたときに返されるという意味ではありませんでした: 意図は、いつでもsgetc()返されることでしたEOF(今 std::char_traits<>::eof()、これは記憶され、ストリームstreambuf をそれ以上呼び出すことはありません。

実際に言えば、実際には 2 つ必要です。1eof()つは上記のように内部で使用するためのもので、もう 1 つはファイルの終わりに達したことが原因で失敗したことを確実に示すものです。そのままでは、次のようなものが与えられます。

std::istringstream s( "1.23e+" );
s >> aDouble;

エラーの原因がフォーマット エラーであることを検出する方法はなく、ストリームにデータがないことを検出する方法はありません。この場合、内部 eof は true を返す必要があります (先を見越したときにファイルの終わりを確認したため、抽出関数へのそれ以降のすべての呼び出しを抑制したいため streambuf) が、データが存在していたため、外部 eof は false である必要があります。 (最初の空白をスキップした後でも)。

もちろん、extractor 関数を実装していない場合は、ios_base::eof()実際に入力エラーが発生するまでテストを行うべきではありません。これが有用な情報を提供することを意図したものでは決してありませんでした (なぜ彼らが定義したのか疑問に思うでしょう— それがifをios_base::good()返すという事実は、 を返さない限り信頼できる情報を提供できないことを意味します。その時点で、それが を返すことがわかっているので、呼んでも意味がない)。falseeof()fail()truefalse

そして、あなたの問題が何であるかわかりません。ストリームは、次の入力がどうなるかを事前に知ることができないため (たとえば、空白をスキップするかどうか)、次の入力がファイルの終わりで失敗するかどうかを事前に知ることができません。採用されているイディオムは明確です。入力を試してから、成功したかどうかをテストします。他の方法は実装できないため、他に方法はありません。Pascal は別の方法でそれを行いましたが、Pascal のファイルは型付きでした。ファイルから 1 つの型しか読み取ることができなかったため、フードの下で常に 1 つの要素を先読みし、この先読みが失敗した場合はファイルの終わりを返すことができました。ファイルから複数のタイプを読み取ることができるようにするために、暫定的なファイルの終わりがないという代償を払っています。

于 2012-11-02T23:38:32.763 に答える
0

動作はやや微妙です。 eofbitファイルの末尾を超えて読み取ろうとしたときに設定されますが、現在の抽出操作が失敗する場合とそうでない場合があります。

例えば:

ifstream blah;
// assume the file got opened
int i, j;
blah >> i;
if (!blah.eof())
    blah >> j;

ファイルに が含まれている場合142<EOF>、一連の数字はファイルの終わりで終了するため、eofbitが設定され、かつ抽出が成功します。jファイルの終わりが既に検出されているため、 の抽出は試行されません。

ファイルに が含まれている場合142 <EOF>、一連の数字は空白で終了します (抽出はi成功します)。 eofbitはまだ設定されていないためblah >> j実行され、数字が見つからずにファイルの終わりに到達するため、失敗します。

ファイルの末尾にある無害に見える空白によって動作がどのように変化したかに注目してください。

于 2012-11-02T23:39:37.750 に答える