[英]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.