2010-11-11 143 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