【问题标题】:C++ istream tellg()/fail() on eof: behavior change; work-around?C++ istream tellg()/fail() on eof: 行为改变;解决方法?
【发布时间】: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 允许。不知道版本。

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

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

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

// 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;
}

【问题讨论】:

    标签: c++ qt gcc istream


    【解决方案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
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-04
    • 2012-09-14
    • 1970-01-01
    • 2011-06-19
    • 1970-01-01
    • 1970-01-01
    • 2022-01-23
    相关资源
    最近更新 更多