3

<<ストリーム演算子を使用して、オブジェクトの逆シリアル化ルーチンを実装しました。ルーチン自体はistreambuf_iterator<char>、オブジェクトを構築するために、を使用してストリームから文字を1つずつ抽出します。

最終的に、私の目標は、を使用してストリームを反復処理し、istream_iterator<MyObject>各オブジェクトをに挿入できるようにすることvectorです。ストリームの終わりに達したときに反復istream_iterator停止するのに問題があることを除いて、かなり標準的です。今のところistream::tellg()、ファイルの最後にいることを示す呼び出しがあったとしても、それは永遠にループします。

問題を再現するためのコードは次のとおりです。

struct Foo
{
    Foo() { }    
    Foo(char a_, char b_) : a(a_), b(b_) { }

    char a;
    char b;
};

// Output stream operator
std::ostream& operator << (std::ostream& os, const Foo& f)
{
    os << f.a << f.b;
    return os;
}

// Input stream operator
std::istream& operator >> (std::istream& is, Foo& f)
{
    if (is.good()) 
    {
        std::istreambuf_iterator<char> it(is);
        std::istreambuf_iterator<char> end;

        if (it != end) {
            f.a = *it++;
            f.b = *it++;
        }
    }
    return is;
}

int main()
{
    {
        std::ofstream ofs("foo.txt");
        ofs << Foo('a', 'b') << Foo('c', 'd');
    }

    std::ifstream ifs("foo.txt");
    std::istream_iterator<Foo> it(ifs);
    std::istream_iterator<Foo> end;
    for (; it != end; ++it) cout << *it << endl; // iterates infinitely
}

この些細な例では、istreambuf_iteratorも必要ないことはわかっていますが、問題を単純化しようとしているだけなので、人々が私の質問に答える可能性が高くなります。

したがって、ここでの問題はistreambuf_iterator、がストリームバッファの最後に到達しても、実際のストリーム自体が状態にならないことEOFです。ファイルの最後のバイトを返し、trueと比較しistream::eof()てもfalseを返すように呼び出します。これは、私が間違いなくストリームの最後にいることを意味します。istream::tellg()istreambuf_iterator<char>(ifs)istreambuf_iterator<char>()

istream_iteratorIOstreamsライブラリコードを調べて、が終了位置にあるかどうかを正確に判断する方法を確認しました。基本的には、にistream::operator void*() const評価されるかどうかを確認しtrueます。このistreamライブラリ関数は単に以下を返します。

return this->fail() ? 0 : const_cast<basic_ios*>(this);

0つまり、フェイルビットが設定されている場合は(false)を返します。次に、この値をのデフォルトで構築されたインスタンスの同じ値と比較istream_iteratorして、最後にいるかどうかを判断します。

そのため、比較が真と終了イテレータを比較std::istream& operator >> (std::istream& is, Foo& f)するときに、ルーチンでフェイルビットを手動で設定してみました。istreambuf_iteratorこれは完全に機能し、ループを適切に終了しました。しかし今、私は本当に混乱しています。「ストリームの終わり」の状態を示すために、istream_iterator 間違いなくチェックしているようです。std::ios::failbitしかし、それは何std::ios::eofbitのためではありませんか?failbitたとえば、の基になるファイルをfstream開くことができなかった場合など、エラー状態のためだと思いました。

では、なぜistream::setstate(std::ios::failbit)ループを終了させるために呼び出す必要があるのでしょうか。

4

5 に答える 5

7

istreambuf_iteratorを使用する場合、istreamオブジェクトの基になるstreambufオブジェクトを操作しています。streambufオブジェクトはその所有者(istreamオブジェクト)について何も知らないため、streambufオブジェクトで関数を呼び出してもistreamオブジェクトは変更されません。そのため、eofに到達したときにistreamオブジェクトのフラグが設定されません。

このようなことをします:

std::istream& operator >> (std::istream& is, Foo& f)
{
    is.read(&f.a, sizeof(f.a));
    is.read(&f.b, sizeof(f.b));
    return is;
}

編集

私はデバッガーでコードをステップスルーしていましたが、これが私が見つけたものです。istream_iteratorには2つの内部データメンバーがあります。関連するistreamオブジェクトへのポインター、およびテンプレートタイプのオブジェクト(この場合はFoo)。++ itを呼び出すと、次の関数が呼び出されます。

void _Getval()
{    // get a _Ty value if possible
    if (_Myistr != 0 && !(*_Myistr >> _Myval))
        _Myistr = 0;
}

_Myistrはistreamポインターであり、_MyvalはFooオブジェクトです。ここを見れば:

!(*_Myistr >> _Myval)

ここで、演算子>>オーバーロードが呼び出されます。そして、それはオペレーターを呼び出します!返されたistreamオブジェクト。そして、あなたがここで見ることができるように、オペレーター!failbitまたはbadbitが設定されている場合にのみtrueを返し、eofbitはそれを行いません。

したがって、次に何が起こるか、failbitまたはbadbitのいずれかが設定されている場合、istreamポインタはNULLになります。そして、次にイテレータを終了イテレータと比較するときに、両方でNULLであるistreamポインタを比較します。

于 2010-11-11T01:53:43.737 に答える
4

外側のループ(最後に到達したことを確認している場所)は、の継承istream_iteratorに格納されている状態に関連付けられています。の状態は、基になるの状態ではなく、それ自体に対して実行された最近の抽出操作の結果を表します。istreamios_baseistreamistreamstreambuf

istreambuf_iteratorから文字を抽出するために使用している内部ループは、 (for )や(for )などstreambufの低レベルの関数を使用しています。これらの関数はどちらも、2番目の関数が進むことを除いて、副作用として状態フラグを設定しません。basic_streambuf::sgetc()operator*basic_streambuf::sbumpc()operator++basic_streambuf::gptr

内側のループは正常に機能しますが、そのままパッケージ化された卑劣な方法で実装されており、のコントラクトにstd::basic_istream& operator>>(std::basic_istream&, T&)違反しています。関数が意図したとおりに要素を抽出できない場合は、呼び出す必要があります。basic_ios::setstate(badbit)抽出中にストリームの終わりも検出した場合は、も呼び出す必要がありますbasic_ios::setstate(eofbit)。抽出機能は、の抽出に失敗したときにどちらのフラグも設定しませんFoo

istreambuf_iteratorレベルで機能することを目的とした抽出演算子を実装するための使用を避けるために、ここでの他のアドバイスに同意しistreamます。あなたはistream契約を維持するために余分な仕事をしなければなりません。それはあなたをここに連れてきたような他の下流の驚きを引き起こします。

于 2010-11-12T03:42:42.833 に答える
2

を正常に読み取れなかった場合は、いつでもoperator>>設定する必要があります。さらに、ファイルの終わりを検出するたびに設定する必要があります。これは次のようになります。failbitFooeofbit

// Input stream operator
std::istream& operator >> (std::istream& is, Foo& f)
{
    if (is.good()) 
    {
        std::istreambuf_iterator<char> it(is);
        std::istreambuf_iterator<char> end;

        std::ios_base::iostate err = it == end ? (std::ios_base::eofbit |
                                                  std::ios_base::failbit) :
                                                 std::ios_base::goodbit;
        if (err == std::ios_base::goodbit) {
            char a = *it;
            if (++it != end)
            {
                char b = *it;
                if (++it == end)
                    err = std::ios_base::eofbit;
                f.a = a;
                f.b = b;
            }
            else
                err = std::ios_base::eofbit | std::ios_base::failbit;
        }
        if (err)
            is.setstate(err);
    }
    else
        is.setstate(std::ios_base::failbit);
    return is;
}

読み取りに失敗した場合はfailbitを設定し、ファイルのeofを検出した場合はeofbitを設定するこのエクストラクタを使用すると、ドライバは期待どおりに機能します。特に、アウターif (is.good())が故障した場合でも、を設定する必要があることに注意してくださいfailbit。ストリームは!good()、のみeofbitが設定されているためである可能性があります。

istream::sentry外側のテストにを使用すると、上記を少し簡略化できます。sentry失敗した場合は、次のように設定さfailbitれます。

// Input stream operator
std::istream& operator >> (std::istream& is, Foo& f)
{
    std::istream::sentry ok(is);
    if (ok) 
    {
        std::istreambuf_iterator<char> it(is);
        std::istreambuf_iterator<char> end;

        std::ios_base::iostate err = it == end ? (std::ios_base::eofbit |
                                                  std::ios_base::failbit) :
                                                 std::ios_base::goodbit;
        if (err == std::ios_base::goodbit) {
            char a = *it;
            if (++it != end)
            {
                char b = *it;
                if (++it == end)
                    err = std::ios_base::eofbit;
                f.a = a;
                f.b = b;
            }
            else
                err = std::ios_base::eofbit | std::ios_base::failbit;
        }
        if (err)
            is.setstate(err);
    }
    return is;
}

またsentry、先頭の空白をスキップします。これはあなたが望むものかもしれないし、そうでないかもしれません。歩哨が先頭の空白をスキップしたくない場合は、次のように作成できます。

    std::istream::sentry ok(is, true);

sentry先頭の空白をスキップしているときにファイルの終わりを検出すると、failbitとの両方が設定されeofbitます。

于 2011-02-26T17:49:59.347 に答える
1

2セットのストリームイテレータが相互に干渉しているようです。

私はこれで動作しました:

// Input stream operator
std::istream& operator >> (std::istream& is, Foo& f)
{
    f.a = is.get();
    f.b = is.get();

    return is;
}
于 2010-11-11T01:56:15.853 に答える
0

.equal()終了条件では、比較演算子を使用する代わりに、メソッドを使用する必要があると思います。

for (; !it.equal(end); ++it) cout << *it << endl;

私は通常、これがforループではなくwhileループで実装されているのを目にします。

while ( !it.equal(end)) {
    cout << *it++ << endl;
}

これら2つは同じ効果があると思いますが、(私にとっては)whileループの方が明確です。

注:イテレーターがeofにあるかどうかを確認するために比較演算子を使用している他の多くのスポットがあります。これらはすべて、を使用するように切り替える必要があります.equal()

于 2010-11-11T01:34:14.733 に答える