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

Cha*_*l72 3 c++ iostream istream-iterator istream

我已经使用<<stream运算符为对象实现了反序列化例程.例程本身使用an istreambuf_iterator<char>逐个从流中提取字符,以构造对象.

最终,我的目标是能够使用a迭代流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
}
Run Code Online (Sandbox Code Playgroud)

我知道在这个简单的例子中我甚至不需要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);
Run Code Online (Sandbox Code Playgroud)

换句话说,0如果设置了failbit ,则返回(false).然后,它将此值与默认构造的实例中的相同值进行比较,istream_iterator以确定我们是否在最后.

所以std::istream& operator >> (std::istream& is, Foo& f)istreambuf_iterator比较true到end迭代器时,我尝试在我的例程中手动设置failbit .这非常有效,并且正确地终止了循环.但现在我真的很困惑.似乎istream_iterator 肯定会检查std::ios::failbit以表示"流结束"条件.但这不是什么意思std::ios::eofbit?我认为failbit是错误条件,例如,如果fstream无法打开基础文件或其他东西.

那么,为什么我需要调用istream::setstate(std::ios::failbit)才能终止循环?

Ben*_*ley 6

使用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;
}
Run Code Online (Sandbox Code Playgroud)

编辑

我在调试器中单步执行代码,这就是我找到的.istream_iterator有两个内部数据成员.指向关联的istream对象的指针,以及模板类型的对象(在本例中为Foo).当你调用++它时,它会调用这个函数:

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

_Myistr是istream指针,_Myval是Foo对象.如果你看这里:

!(*_Myistr >> _Myval)
Run Code Online (Sandbox Code Playgroud)

这就是它所谓的操作员>>过载.它叫操作员!在返回的istream对象上.正如你在这里看到的,运营商!如果设置了failbit或badbit,则只返回true,eofbit不会这样做.

那么,接下来会发生什么,如果设置了failbit或badbit,则istream指针变为NULL.下次将迭代器与结束迭代器进行比较时,它会比较istream指针,它们都是NULL.