【问题标题】:Reading with setw: to eof or not to eof?用 setw 阅读:到 eof 还是不到 eof?
【发布时间】:2014-12-15 19:46:57
【问题描述】:

考虑以下简单示例

#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 操作符指定的,我希望&gt;&gt; 操作符在成功地从输入流中提取所请求的字符数后完​​成对字符串的读取。我看不出它有什么直接的理由尝试提取第七个字符,这意味着我不希望流进入eof 状态。

当我在 MSVC++ 下运行此示例时,它按我的预期工作:读取后流保持良好状态。但是,在 GCC 中,行为有所不同:流以 eof 状态结束。

语言标准,它给出了这个版本的&gt;&gt;操作符的完成条件列表

  • 存储了 n 个字符;
  • 文件结束出现在输入序列上;
  • isspace(c,is.getloc()) 对于下一个可用的输入字符 c 为真。

鉴于上述情况,我看不出有任何理由让&gt;&gt; 运算符在上述代码中将流驱动到eof 状态。

不过,&gt;&gt; 运算符 implementation in GCC library 看起来是这样的

...
__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 的行为都符合吗?还是只有其中一个行为正确?

【问题讨论】:

  • 我认为规范不需要检查列表以短路(甚至按该顺序),如果不需要短路则第三项(检查@987654337 @ 表示下一个可用的输入字符)将要求它准备下一个字符。
  • libc++ 同意 MSVC。
  • 我没有标准文本来支持这一点,但我的理解是是否读取最后一个字符集eofbit 是可选的。 (在阅读最后一个字符后尝试阅读肯定会设置它)。

标签: c++ visual-c++ gcc iostream c++-standard-library


【解决方案1】:

该特定operator&gt;&gt; 的定义与eofbit 的设置无关,因为它仅描述操作何时终止,而不是触发特定位的原因。

标准(草案)中eofbit的描述是:

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

我想这取决于你想如何解释“到达”。注意gcc实现正确没有设置failbit,其定义为

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

所以我认为eofbit 并不一定意味着文件末尾阻碍了任何新字符的提取,只是文件末尾已经“到达”。

我似乎无法为“达到”找到更准确的描述,所以我想这将是实现定义的。如果这个逻辑是正确的,那么 MSVC 和 gcc 的行为都是正确的。


编辑:特别是,当sgetc() 返回eof 时,似乎设置了eofbit。这在istreambuf_iterator 部分和basic_istream::sentry 部分都有描述。那么现在的问题是:流的当前位置什么时候允许前进?


最终编辑:事实证明,g++ 可能具有正确的行为。

每个字符扫描都经过&lt;locale&gt;,以便解析不同的字符集、货币格式、时间描述和数字格式。虽然似乎没有关于operator&gt;&gt; 如何处理字符串的完整描述,但对于do_get 如何处理数字、时间和金钱应该如何运作有非常具体的描述。您可以从前稿的第 687 页找到它们。

所有这些都始于从istreambuf_iterator 读取ctype(字符的“全局”版本,通过语言环境读取)(对于数字,您可以在草稿的第 1018 页找到调用定义)。然后处理ctype,最后进阶迭代器。

因此,一般来说,这要求内部迭代器始终指向读取最后一个字符后的下一个字符;如果不是这种情况,理论上你可以提取比你想要的更多的东西:

string str = "strin1";
istringstream is(str);
is >> setw(6) >> str;
int x;
is >> x;

如果str 提取后is 的当前字符不在eof 上,则标准将要求x 获取值1,因为对于数字提取标准明确要求迭代器在第一次读取后前进。

由于这没有多大意义,并且考虑到标准中描述的所有复杂提取的行为方式相同,因此对于字符串也会发生同样的情况是有道理的。因此,由于is的指针在读取6个字符后落在eof上,需要设置eofbit

【讨论】:

  • 提取或不提取字符对eof 没有影响。当sgetc 返回eof 时设置eofbit;等效地,当*it 返回eof,其中it 是内部流迭代器。 operator&gt;&gt; 确实在提取 n 字符后停止。由于现在流迭代器指向提取后的字符,并且该字符是eof,因此它设置eofbit
猜你喜欢
  • 1970-01-01
  • 2018-05-26
  • 1970-01-01
  • 1970-01-01
  • 2017-06-25
  • 2017-10-21
  • 2020-04-02
  • 1970-01-01
  • 2016-09-11
相关资源
最近更新 更多