使用setw函数读取:到达文件结尾(eof)还是不到结尾?

7
考虑以下简单示例。
#include <string>
#include <sstream>
#include <iomanip>

using namespace std;

int main() {
  string str = "string";
  istringstream is(str);
  is >> setw(6) >> str;
  return is.eof();
}

乍一看,由于setw操作符指定了显式宽度,我期望>>运算符在成功从输入流中提取所需数量的字符后就完成字符串读取。我没有看到任何直接的理由让它尝试提取第七个字符,这意味着我不希望流进入eof状态。
当我在MSVC++下运行此示例时,它按照我的预期工作:流在读取后保持良好状态。但是,在GCC中,行为不同:流最终处于eof状态。
语言标准给出了此版本>>操作符的以下完成条件列表:
n个字符被存储;
在输入序列上发生文件结束;
isspace(c,is.getloc())对于下一个可用的输入字符c为真。
鉴于上述情况,我不认为>>操作符有任何理由将流驱动到上述代码中的eof状态。

然而,这就是>>运算符在GCC库中的实现的样子。

...
__int_type __c = __in.rdbuf()->sgetc();

while (__extracted < __n
       && !_Traits::eq_int_type(__c, __eof)
       && !__ct.is(__ctype_base::space,
                   _Traits::to_char_type(__c)))
{
  if (__len == sizeof(__buf) / sizeof(_CharT))
  {
    __str.append(__buf, sizeof(__buf) / sizeof(_CharT));
    __len = 0;
  }
  __buf[__len++] = _Traits::to_char_type(__c);
  ++__extracted;
  __c = __in.rdbuf()->snextc();
}
__str.append(__buf, __len);

if (_Traits::eq_int_type(__c, __eof))
  __err |= __ios_base::eofbit;
__in.width(0);
...

正如您所看到的,在每次成功迭代结束时,它都会尝试为下一次迭代准备下一个__c字符,即使下一次迭代可能永远不会发生。在循环之后,它分析了那个__c字符的最后一个值,并相应地设置了eofbit
因此,我的问题是:在上述情况下触发eof流状态,就像GCC所做的那样——从标准的角度来看,这是合法的吗?我没有在文档中明确指定。MSVC和GCC的行为是否符合规范?还是只有其中一个行为正确?

我认为规范并不要求检查列表进行短路(甚至不需要按顺序),如果不需要短路,那么第三项(检查下一个可用输入字符的isspace)将需要准备下一个字符。 - T.C.
libc++ 与 MSVC 兼容。 - T.C.
我没有标准文本来支持这一点,但我的理解是,读取最后一个字符集是否设置eofbit是可选的。(在读取最后一个字符之后尝试读取确实会设置它)。 - M.M
1个回答

2
那个特定的operator>>的定义与设置eofbit不相关,因为它只描述操作何时终止,而不是触发特定位的内容。
标准(草案)中eofbit的描述如下:

eofbit - 表示输入操作到达输入序列的结尾;

我猜这里取决于你如何解释“到达”。请注意,gcc实现正确地没有设置failbit,它被定义为:

failbit - 表示输入操作未能读取预期字符,或输出操作未能生成所需字符。

因此,我认为eofbit不一定意味着文件结束妨碍了任何新字符的提取,只是已经“到达”文件结束。
我似乎找不到更准确的“到达”描述,所以我想那可能是实现定义的。如果这个逻辑是正确的,那么MSVC和gcc的行为都是正确的。
编辑:特别是,当sgetc()返回eof时,看起来会设置eofbit。这在istreambuf_iterator部分和basic_istream::sentry部分都有描述。那么现在的问题是:流的当前位置何时允许前进?
最终编辑:事实证明,g++可能有正确的行为。
每个字符扫描都通过<locale>,以允许解析不同的字符集、货币格式、时间描述和数字格式。虽然似乎没有关于如何为字符串工作的operator>>的详细说明,但是对于数字、时间和货币的do_get函数的操作方式有非常具体的描述。你可以从草案的第687页开始找到它们。
所有这些都始于从istreambuf_iterator(对于数字,您可以在草案的第1018页找到调用定义)读取一个ctype(通过区域设置读取的“全局”字符)。然后处理ctype,最后将迭代器推进。
因此,一般来说,这要求内部迭代器始终指向上次读取之后的下一个字符;如果不是这种情况,理论上你可以提取更多的内容。
string str = "strin1";
istringstream is(str);
is >> setw(6) >> str;
int x;
is >> x;

如果str提取后的当前字符不在eof上,则标准要求对于数字提取,第一次读取后迭代器必须被推进,因此x应该获得值1。
由于这并没有太多意义,并且考虑到标准中描述的所有复杂提取都表现相同,因此对于字符串来说也应该如此。因此,由于读取6个字符后is指针落在eof上,所以需要设置eofbit

提取或不提取字符对 eof 没有影响。当 sgetc 返回 eof 时,eofbit 被设置;同样地,当 *it 返回 eof 时,其中 it 是内部流迭代器。operator>> 在提取 n 个字符后会停止。由于现在流迭代器指向已提取字符之后的字符,并且该字符是 eof,因此它设置了 eofbit - Svalorzen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接