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

时间:2010-11-11 01:16:21

标签: c++ iostream istream istream-iterator

我使用<<流运算符为对象实现了反序列化例程。例程本身使用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)将true与结束迭代器进行比较时,我尝试在istreambuf_iterator例程中手动设置failbit。这非常有效,并且正确地终止了循环。但现在我真的很困惑。似乎istream_iterator 肯定检查std::ios::failbit以表示“流结束”条件。但这不是std::ios::eofbit的意思吗?我认为failbit用于错误条件,例如,如果无法打开fstream的基础文件或其他内容。

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

5 个答案:

答案 0 :(得分: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;
}

修改

我正在调试我的调试器中的代码,这就是我找到的。 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)

这就是它所谓的运营商&gt;&gt;超载。它叫操作员!在返回的istream对象上。正如你可以看到here,操作员!如果设置了failbit或badbit,则只返回true,eofbit不会这样做。

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

答案 1 :(得分:3)

您的外部循环 - 您要检查istream_iterator是否已到达终点 - 与istream继承的ios_base中存储的状态相关联。 istream上的状态表示针对 istream本身执行的最近提取操作的结果,而不是其基础streambuf的状态。

您的内部循环 - 您使用istreambuf_iteratorstreambuf中提取字符的位置 - 正在使用较低级别的功能,例如basic_streambuf::sgetc()(适用于operator*)和{ {3}}(适用于basic_streambuf::sbumpc())。除了推进operator++的第二个函数之外,这些函数都没有将状态标志设置为副作用。

你的内循环工作正常,但它是以一种偷偷摸摸的方式实现打包的,它违反了basic_streambuf::gptr。如果函数无法按预期提取元素,则必须调用basic_ios::setstate(badbit),如果在提取时遇到流末尾,则还必须调用basic_ios::setstate(eofbit)。当提取器函数无法提取Foo时,您的提取器函数都不会设置标记。

我同意这里的其他建议,以避免使用istreambuf_iterator来实现旨在istream级别工作的提取运算符。你强迫自己做额外的工作来维持istream合同,这将导致其他下游的惊喜,比如带你到这里的那个。

答案 2 :(得分:2)

operator>> failbit中,只要您未能成功阅读Foo,就应设置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; } 。这看起来像这样:

if (is.good())

使用此提取程序,在设置失败时设置failbit,并在检测文件eof时进行eofbit,您的驱动程序按预期工作。请特别注意,即使您的外部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和{{1}}。

答案 3 :(得分:1)

看起来两组流迭代器互相干扰:

我得到了它的工作:

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

    return is;
}

答案 4 :(得分:0)

我认为您的最终条件需要使用.equal()方法,而不是使用比较运算符。

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

我通常看到这是用while循环而不是for循环实现的:

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

我认为这两个会产生相同的效果,但(对我而言)while循环更清晰。

注意:您有许多其他位置,您正在使用比较运算符来检查迭代器是否处于eof。所有这些都应该切换为使用.equal()