我使用<<
流运算符为对象实现了反序列化例程。例程本身使用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)
来终止循环?
答案 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_iterator
从streambuf
中提取字符的位置 - 正在使用较低级别的功能,例如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()
。