3

我已经使用<<流运算符为对象实现了反序列化例程。例程本身使用一个istreambuf_iterator<char>从流中一个一个地提取字符,以构造对象。

最终,我的目标是能够使用 an 遍历流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 库代码,以确切了解它如何确定 anistream_iterator是否位于结束位置,并且基本上它检查是否istream::operator void*() const评估为true. 这个 istream 库函数只返回:

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

换句话说,0如果设置了故障位,则返回 (false)。然后它将这个值与默认构造的实例中的相同值进行比较,istream_iterator以确定我们是否在最后。

因此,当与结束迭代器比较为真时,我尝试在std::istream& operator >> (std::istream& is, Foo& f)例程中手动设置故障位。istreambuf_iterator这完美地工作,并正确终止了循环。但现在我真的很困惑。似乎istream_iterator 肯定会检查std::ios::failbit以表示“流结束”条件。但这不std::ios::eofbit就是为了什么吗?我认为failbit是针对错误情况,例如,如果fstream无法打开 an 的基础文件或其他情况。

那么,为什么我需要调用istream::setstate(std::ios::failbit)来终止循环呢?

4

5 回答 5

7

当您使用 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)。当你调用 ++it 时,它会调用这个函数:

void _Getval()
{    // get a _Ty value if possible
    if (_Myistr != 0 && !(*_Myistr >> _Myval))
        _Myistr = 0;
}

_Myistr 是 istream 指针,_Myval 是 Foo 对象。如果你看这里:

!(*_Myistr >> _Myval)

这就是它调用您的 operator>> 重载的地方。它调用操作员!在返回的 istream 对象上。正如您在这里看到的,操作员!只有在设置了 failbit 或 badbit 时才返回 true,eofbit 不会这样做。

那么,接下来会发生什么,如果设置了failbit 或badbit,则istream 指针将变为NULL。下次您将迭代器与结束迭代器进行比较时,它会比较 istream 指针,这两个指针都为 NULL。

于 2010-11-11T01:53:43.737 回答
4

你的外部循环——你正在检查你istream_iterator是否已经到达终点的地方——与存储在istream' sinherited 中的状态相关联ios_base。上的状态代表最近自身istream执行的提取操作的结果,而不是其底层的状态。istreamstreambuf

你的内部循环——你istreambuf_iterator用来从中提取字符的地方——正在streambuf使用较低级别的函数,如basic_streambuf::sgetc()(for operator*) 和basic_streambuf::sbumpc()(for operator++)。这些函数都没有设置状态标志作为副作用,除了第二个推进basic_streambuf::gptr

你的内部循环工作正常,但它是以一种偷偷摸摸的方式打包的,它违反std::basic_istream& operator>>(std::basic_istream&, T&). 如果函数未能按预期提取元素,则必须调用basic_ios::setstate(badbit),并且如果在提取时还遇到流尾,则还必须调用basic_ios::setstate(eofbit). 您的提取器函数在无法提取Foo.

我同意这里的其他建议,以避免使用istreambuf_iterator来实现旨在在该istream级别工作的提取运算符。你强迫自己做额外的工作来维持istream合同,这将导致其他下游惊喜,比如把你带到这里的那个。

于 2010-11-12T03:42:42.833 回答
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;
}

使用这个提取器,它将failbit 设置为读取失败,并将eofbit 设置为检测文件的eof,您的驱动程序可以按预期工作。请特别注意,即使您的外部if (is.good())失败,您仍然需要设置failbit. 您的流可能是!good()因为 onlyeofbit已设置。

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

于 2011-02-26T17:49:59.347 回答
1

看起来两组流迭代器相互干扰:

我得到了它的工作:

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

    return is;
}
于 2010-11-11T01:56:15.853 回答
0

I think your end condition needs to use the .equal() method, instead of using the comparison operator.

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

I usually see this implemented with a while loop instead of a for loop:

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

I think those two will have the same effect, but (to me) the while loop is clearer.

Note: You have a number of other spots where you're using the comparison operator to check whether an iterator is at eof. All of these should probably be switched to use .equal().

于 2010-11-11T01:34:14.733 回答