C++ istream tellg()/fail() on eof: 行为改变;解决方法?

Posted

技术标签:

【中文标题】C++ istream tellg()/fail() on eof: 行为改变;解决方法?【英文标题】:C++ istream tellg()/fail() on eof: behavior change; work-around? 【发布时间】:2013-08-28 14:19:42 【问题描述】:

我将编译器从 gcc-4.4 升级到 gcc-4.8,但由于以下(错误)假设,一个项目惨遭失败:

#include <sstream>
#include <assert.h>

int main()

    using namespace std;
    istringstream iScan;
    int num;

    //iScan.unsetf(std::ios::skipws);
    iScan.str("5678");
    iScan >> num;
    assert(iScan.tellg() == istringstream::pos_type(4));
    assert(!iScan.fail());
    assert(!iScan.good());
    assert(iScan.eof());
    assert(num == 5678);
    assert(false && "We passed the above assertions.");
    return 0;

在 gcc-4.4 上,相关断言通过。在 gcc-4.8 上,tellg() 返回 -1 并且 fail() 返回 !false,显然是因为它遇到了 eof。

我的目标是 Qt 5.1 (gcc-4.8) 附带的 MinGW 32 位。

问题:

按照N3168 或其他方式,旧行为真的有误吗? (还有哪些?) 是否有一个全球性的、可靠的、独立于语言的解决方法? (我猜不是。) 是否存在跨版本的全局、可靠的 gcc 解决方法? 即使我执行了上述 unsetf(skipws),它仍然无法在 gcc-4.8 上运行。这不是 不正确的行为吗?

此外,各种在线编译器会给出不同的行为。这是他们的库的功能吗?

声称是 gcc-4.7.2 的 compileonline 允许它,即使其他消息来源说行为在 4.6 中发生了变化。 stack-crooked,gcc-4.8,显示了新的行为,unsetf(skipws) 好像没有效果。 codepad 允许。不知道版本。

其他类似但不重复的问题:

file stream tellg/tellp and gcc-4.6 is this a bug? GCC 4.7 istream::tellg() returns -1 after reaching EOF

包含这些假设的代码体很大。

更新:这是答案的关键部分,它应该适用于所有版本、所有编译器:

// istream::tellg() is unreliable at eof(): works w/gcc-4.4, doesn't w/gcc-4.8.
#include <sstream>
#include <assert.h>

using namespace std;
typedef istream::pos_type   pos_type;

pos_type reliable_tellg(istream &iScan)
    
    bool wasEOF = iScan.eof();
    if (wasEOF)
        iScan.clear(iScan.rdstate() & ~ios::eofbit); // so tellg() works.
    pos_type r = iScan.tellg();
    if (wasEOF)
        iScan.clear(iScan.rdstate() | ios::eofbit); // restore it.
    return r;
    


int main()

    istringstream iScan;
    int num, n2;

    //iScan.unsetf(std::ios::skipws);
    iScan.str("5678");
    assert(!iScan.eof() && !iScan.fail()); // pre-conditions.
    assert(reliable_tellg(iScan) == pos_type(0));

    iScan >> num;
    assert(!iScan.fail());
    assert(reliable_tellg(iScan) == pos_type(4));
    assert(iScan.eof());
    assert(reliable_tellg(iScan) == pos_type(4)); // previous calls don't bungle it.
    assert(num == 5678);

    iScan >> n2; // at eof(), so this should fail.
    assert(iScan.fail());
    assert(reliable_tellg(iScan) == pos_type(-1)); // as expected on fail()
    assert(iScan.eof());

    assert(false && "We passed the above assertions.");
    return 0;

【问题讨论】:

【参考方案1】:

您似乎期望的行为可能是错误的。两者都是 C++11 和 C++03 开始​​ tellg 的描述与“行为 未格式化的输入函数[...]”。一个“未格式化的输入 函数”首先构造一个sentry 对象,并将 失败,什么都不做并返回失败状态,如果 sentry 对象转换为 false。还有sentry 对象 如果设置了eofbit,将转换为false

标准对于是否阅读 number 设置eofbit,但只是稍微设置(使用 信息分布在几个不同的部分)。 基本上,当输入一个数值时,流(实际上, num_get facet) 必须提前读取一个字符,以便 知道号码在哪里结束。在你的情况下,它会看到结束 发生这种情况时的文件,因此将设置eofbit。所以你的 第一个 assert 将失败,实现一致。

人们很容易认为这是标准中的缺陷,或者 无意的。很容易想象一些实现 做明智的事情(这似乎是你所期望的), 也许是因为最初的实现者没有意识到 标准中的全部含义(或无意识地将其读作 他们认为应该读)。我猜这是 g++ 的情况,当他们意识到他们的行为是 不符合,他们修好了。

至于解决方法...我不确定真正的问题是什么, 您正在尝试解决的问题。但我认为如果你 清除tellg 之前的错误位,它应该可以工作。 (的 当然,那么iScan.good() 将是trueiScan.eof() false。但这真的很重要吗?)一定要检查一下 在您清除之前提取实际上已成功 状态。

【讨论】:

那么,正如您所说,提前读取一个字符,规范是否明确指出数字在 eof 时没有失败? 在eof,即使你清除了eofbit,调用tellg()也只是重新设置,不是吗? @JimB 标准很明确。转换数值时,将提取字符直到遇到文件的任一结尾,或者看到不能是数字(任何类型或基数)的一部分的字符。如果此提取过程因文件结束而终止,则设置 eofbit。 (这在第 22.4.2.1.2 节第 3 段的第 2 阶段中进行了描述。) @JimB No. eofbit 已设置,因为尝试读取字符(无论是否提取)导致文件结束。 tellg 不会尝试读取字符。 eofbit预测性的。它不会被设置,因为下一次读取将看到文件结尾,它被设置是因为尝试(内部)读取字符失败;对streambuf::sgetsstreambuf::sbumpsstreambuf::snexts 的调用已返回EOF

以上是关于C++ istream tellg()/fail() on eof: 行为改变;解决方法?的主要内容,如果未能解决你的问题,请参考以下文章

C++流输入istream的成员函数及其用法

如何将 istream 传递给 c++ 中的函数?

尝试重载的 C++ 中的“ostream”和“istream”出错

C++ 从 istream 读取 int,检测溢出

我对 c++ istream 赋值很好奇

从 C++ 中的 istream 对象读取时如何检测空行?