[英]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_iterator
从streambuf
-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
在跳过前导空格时检测到文件结束,则会设置failbit
和eofbit
。
看起来两组流迭代器互相干扰:
我得到了它:
// 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.