2010-11-11 146 views
3

我使用<<流操作符實現了一個對象的反序列化例程。該例程本身使用istreambuf_iterator<char>來逐個從流中提取字符,以構建該對象。關於`std :: istreambuf_iterator`的使用感到困惑

最終,我的目標是能夠使用istream_iterator<MyObject>迭代流並將每個對象插入vector。漂亮的標準,除非我在獲取istream_iterator停止遇到碼流結束時迭代。現在,它只是永遠循環播放,即使撥打​​表示我在文件末尾。

這裏的代碼來重現問題:

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,即使​​返回文件中的最後一個字節,並且istreambuf_iterator<char>(ifs)istreambuf_iterator<char>()相比較,意味着我肯定在流的末尾。

我看着Iostreams庫代碼,看看它究竟是如何確定istream_iterator是否處於末端位置,基本上它會檢查是否istream::operator void*() const計算結果爲true。這istream的庫函數返回:

return this->fail() ? 0 : const_cast<basic_ios*>(this); 

換句話說,它返回0(假)如果failbit設置。然後它將該值與默認構建的實例istream_iterator中的相同值進行比較,以確定我們是否在最後。

所以我試着在我的std::istream& operator >> (std::istream& is, Foo& f)例程中手動設置失敗位,當istreambuf_iterator與最終迭代器比較真時。這工作完美,並正確終止循環。但現在我真的很困惑。看來,istream_iterator肯定檢查std::ios::failbit爲了表示「流結束」條件。但是,這不是std::ios::eofbit的用途嗎?我認爲failbit是出於錯誤條件,例如,如果fstream的底層文件無法打開或某事。

那麼,爲什麼我需要撥打istream::setstate(std::ios::failbit)才能讓循環終止?

+0

循環永久指示流已經變質。問題是爲什麼? – 2010-11-11 01:50:04

+0

@Martin,好吧,即使我用'std :: stringstream'替換文件流,也會發生同樣的問題。所以這不能是某種低級文件相關的問題。 – Channel72 2010-11-11 01:53:57

+0

閱讀@ PigBen的回答。原因是在外層你使用istream_iterator(在for_each)和istreambuf_iterator在內部(operatro >>)。您的使用需要保持一致。在這兩種情況下使用istreambuf_iterators,它應該工作。 – 2010-11-11 18:17:08

回答

5

當您使用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對象上。正如你所看到的here,運營商!只有當failbit或badbit被設置時才返回true,eofbit不會這樣做。

因此,接下來會發生什麼,如果failbit或badbit被設置,則istream指針將被取NULL。下一次將迭代器與最終迭代器進行比較時,它將比較兩者都爲NULL的istream指針。

+0

我真的更喜歡使用istreambuf_iterator,因爲它允許我重複使用與其他類型的迭代器相同的例程。 (例如,當我的對象通過'string :: iterator'存儲在'std :: string'中時,我可以反序列化我的對象。)但是我明白你在說什麼 - 兩組迭代器不通信。那麼,爲什麼當'istreambuf_iterator'到達末尾時我只是在'istream'對象上手動調用'istream :: setstate(std :: ios :: eofbit)'時,它不起作用呢? – Channel72 2010-11-11 02:03:07

+0

我想這是因爲當你比較一個迭代器到流結束迭代器時,它會檢查failbit而不是eofbit。這樣做是有道理的,因爲在istream對象的正常操作中(使用操作符>>),每當eofbit被設置時,failbit也被設置。但是,相反的情況並非總是如此,因此檢查失敗位更有意義。在你的函數中,你應該模仿操作符>>的行爲並設置它們。 – 2010-11-11 02:28:02

0

我認爲您的最終條件需要使用.equal()方法,而不是使用比較運算符。

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

我通常看到這個while循環,而不是一個for循環來實現:

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

我想這兩個將具有相同的效果,但(對我來說)while循環更清晰。

注意:您還有許多其他位置正在使用比較運算符來檢查迭代器是否在eof處。所有這些應該可能切換到使用.equal()

1

它看起來像兩套流迭代器都interfearing與對方:

我得到了它這個工作:

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

    return is; 
} 
+0

好的 - 查看我對PigBen的評論。我真的更喜歡使用'std :: istreambuf_iterator',因爲迭代器的使用允許我編寫可以在任何容器上工作的通用例程,而不是僅適用於流的例程。 – Channel72 2010-11-11 02:03:55

2

你外環—你在哪裏檢查您istream_iterator到已達到其結尾—綁定到存儲在istream的繼承ios_base狀態。 istream上的狀態代表最近提取操作對istream本身執行的結果,而不是其基礎streambuf的狀態。

你內環—你使用istreambuf_iterator哪裏提取從streambuf —字符使用較低級別的功能,如basic_streambuf::sgetc()(用於operator*)和basic_streambuf::sbumpc()(用於operator++)。這兩種功能都沒有將狀態標誌設置爲副作用,除了第二個功能前進basic_streambuf::gptr

你的內部循環工作正常,但它以一種偷偷摸摸的方式實現,它違反了the contract of std::basic_istream& operator>>(std::basic_istream&, T&)。如果函數無法按預期提取元素,則它必須調用basic_ios::setstate(badbit),並且如果它在提取時遇到了流結束,則它還必須調用basic_ios::setstate(eofbit)。當提取器函數無法提取Foo時,它不會設置標誌。

我同意這裏的其他建議,以避免使用istreambuf_iterator來實現意圖在istream級別工作的提取操作符。你強迫自己做了額外的工作來維持合約,這會引起其他下游的意外事件,例如帶給你的意外。

1

在您的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在檢測文件的EOF,你的驅動程序工作正常。請特別注意,即使您的外部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檢測到文件末尾,而跳過前導空格,它會同時設置failbiteofbit