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()
将是true
和iScan.eof()
false
。但这真的很重要吗?)一定要检查一下
在您清除之前提取实际上已成功
状态。
【讨论】:
那么,正如您所说,提前读取一个字符,规范是否明确指出数字在 eof 时没有失败? 在eof,即使你清除了eofbit,调用tellg()也只是重新设置,不是吗? @JimB 标准很明确。转换数值时,将提取字符直到遇到文件的任一结尾,或者看到不能是数字(任何类型或基数)的一部分的字符。如果此提取过程因文件结束而终止,则设置eofbit
。 (这在第 22.4.2.1.2 节第 3 段的第 2 阶段中进行了描述。)
@JimB No. eofbit
已设置,因为尝试读取字符(无论是否提取)导致文件结束。 tellg
不会尝试读取字符。 eofbit
是非预测性的。它不会被设置,因为下一次读取将看到文件结尾,它被设置是因为尝试(内部)读取字符失败;对streambuf::sgets
、streambuf::sbumps
或streambuf::snexts
的调用已返回EOF
。以上是关于C++ istream tellg()/fail() on eof: 行为改变;解决方法?的主要内容,如果未能解决你的问题,请参考以下文章