簡體   English   中英

對`std :: istreambuf_iterator`的使用感到困惑

[英]Confused about usage of `std::istreambuf_iterator`

我已經使用<< stream運算符為對象實現了反序列化例程。 例程本身使用istreambuf_iterator<char>從流中提取字符,以構造對象。

最終,我的目標是能夠使用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狀態。 調用istream::eof()返回false,即使istream::tellg()返回文件中的最后一個字節, istreambuf_iterator<char>(ifs)將true與istreambuf_iterator<char>()進行比較,這意味着我肯定是在流的最后。

我查看了IOstreams庫代碼,以確切了解它是如何確定istream_iterator是否位於結束位置,並且基本上它檢查istream::operator void*() const是否計算為true 這個istream庫函數只返回:

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

換句話說,如果設置了failbit,則返回0 (false)。 然后,它將此值與istream_iterator的默認構造實例中的相同值進行比較,以確定我們是否在最后。

因此std::istream& operator >> (std::istream& is, Foo& f)istreambuf_iterator將true與結束迭代器進行比較時,我嘗試在std::istream& operator >> (std::istream& is, Foo& f)例程中手動設置failbit。 這非常有效,並且正確地終止了循環。 但現在我真的很困惑。 似乎istream_iterator 肯定會檢查std::ios::failbit以表示“流結束”條件。 但這不是std::ios::eofbit的用途嗎? 我認為failbit是針對錯誤條件的,例如,如果無法打開fstream的基礎文件或其他內容。

那么,為什么我需要調用istream::setstate(std::ios::failbit)來使循環終止?

使用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有兩個內部數據成員。 指向關聯的istream對象的指針,以及模板類型的對象(在本例中為Foo)。 當你調用++它時,它會調用這個函數:

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。 下次將迭代器與結束迭代器進行比較時,它會比較istream指針,它們都是NULL。

您正在檢查istream_iterator到達其結尾的外部循環與存儲在istream的繼承ios_base istream上的狀態表示最近針對istream本身執行的提取操作的結果,而不是其底層streambuf的狀態。

您的內部循環 - 您正在使用istreambuf_iteratorstreambuf -is中提取字符,使用較低級別的函數,如basic_streambuf::sgetc() (對於operator* )和basic_streambuf::sbumpc() (對於operator++ )。 除了第二個提升basic_streambuf::gptr之外,這兩個函數都沒有將狀態標志設置為basic_streambuf::gptr

你的內部循環工作正常,但它以一種偷偷摸摸的方式實現打包,它違反std::basic_istream& operator>>(std::basic_istream&, T&) 如果函數無法按預期提取元素,則必須調用basic_ios::setstate(badbit) ,如果在提取時遇到流末尾,則還必須調用basic_ios::setstate(eofbit) 當提取器函數無法提取Foo時,你的提取器函數既不設置標志。

我同意這里的其他建議,以避免使用istreambuf_iterator來實現一個旨在在istream級別工作的提取運算符。 你強迫自己做額外的工作來維持istream合同,這將導致其他下游的驚喜,比如帶你到這里的那個。

在您的operator>>您應該在無法成功讀取Foo時設置failbit 此外,您應該在檢測到文件結束時設置eofbit 這看起來像這樣:

// 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,並且在檢測文件的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在跳過前導空格時檢測到文件結束,則會設置failbiteofbit

看起來兩組流迭代器互相干擾:

我得到了它:

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

    return is;
}

我認為你的結束條件需要使用.equal()方法,而不是使用比較運算符。

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

我通常看到這是用while循環而不是for循環實現的:

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

我認為這兩個會產生相同的效果,但(對我而言)while循環更清晰。

注意:您有許多其他位置,您正在使用比較運算符來檢查迭代器是否處於eof。 所有這些都應該切換為使用.equal()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM