我使用了 << 流操作符为一个对象实现了反序列化程序。该程序本身使用 istreambuf_iterator 从流中逐个提取字符,以构建对象。
最终,我的目标是能够使用 istream_iterator 迭代流,并将每个对象插入 vector 中。这很常见,但我在让 istream_iterator 在到达流的结尾时停止迭代方面遇到了麻烦。目前,它只是无限循环,即使调用 istream::tellg() 表明我已经到达了文件的末尾。
以下是重现问题的代码:
我知道在这个简单的例子中,我甚至不需要 istreambuf_iterator,但是我只是试图简化问题,以便更有可能得到人们的答案。
所以这里的问题是,即使 istreambuf_iterator 到达了流缓冲区的末尾,实际的流本身也没有进入 EOF 状态。 调用 istream::eof() 返回 false,即使 istream::tellg() 返回文件中的最后一个字节,istreambuf_iterator(ifs) 与 istreambuf_iterator() 相比为 true,这意味着我肯定已经到达了流的末尾。
我查看了 IOstreams 库代码,以确定它如何确定一个 istream_iterator 是否处于末尾位置,基本上它检查 istream::operator void*() const 是否评估为 true。这个 istream 库函数简单地返回:
换句话说,如果设置了failbit,则返回0(false)。 然后,将此值与默认构造的istream_iterator实例中的相同值进行比较,以确定我们是否到达末尾。
因此,当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)才能终止循环呢?
最终,我的目标是能够使用 istream_iterator 迭代流,并将每个对象插入 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(ifs) 与 istreambuf_iterator() 相比为 true,这意味着我肯定已经到达了流的末尾。
我查看了 IOstreams 库代码,以确定它如何确定一个 istream_iterator 是否处于末尾位置,基本上它检查 istream::operator void*() const 是否评估为 true。这个 istream 库函数简单地返回:
return this->fail() ? 0 : const_cast<basic_ios*>(this);
换句话说,如果设置了failbit,则返回0(false)。 然后,将此值与默认构造的istream_iterator实例中的相同值进行比较,以确定我们是否到达末尾。
因此,当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)才能终止循环呢?
std::stringstream
替换文件流,仍然会出现相同的问题。因此,这不可能是某种低级文件相关的问题。 - Channel72