为什么在stringstream >>操作失败时会更改目标值?

56

来自 Stroustrup 的《TC++PL》第3版,第21.3.3节:

如果我们尝试将一个变量 v 进行读取操作并且该操作失败,则变量 v 的值应保持不变(如果 v 是 istream 或 ostream 成员函数处理的类型之一,则它是不变的)。

以下示例似乎与上述引文相矛盾。根据上述引言,我期望 v 的值保持不变,但实际上它被清零了。这种表面上的矛盾行为的解释是什么?

#include <iostream>
#include <sstream>

int main( )
{
    std::stringstream  ss;

    ss  << "The quick brown fox.";

    int  v = 123;

    std::cout << "Before: " << v << "\n";

    if( ss >> v )
    {
        std::cout << "Strange -- was successful at reading a word into an int!\n";
    }

    std::cout << "After: " << v << "\n";

    if( ss.rdstate() & std::stringstream::eofbit  ) std::cout << "state: eofbit\n";
    if( ss.rdstate() & std::stringstream::failbit ) std::cout << "state: failbit\n";
    if( ss.rdstate() & std::stringstream::badbit  ) std::cout << "state: badbit\n";

    return 1;
}

我使用x86_64-w64-mingw32-g++.exe (rubenvb-4.7.2-release) 4.7.2得到的输出是:

Before: 123
After: 0
state: failbit

谢谢。


根据Joachim Pileborg的解释,在这种问题中提到编译器标志是很好的;我正在使用的标志是:-Wall -Wextra -Werror -static -std=c++11 - user1823664
2个回答

63
这个参考文献 中得知:

如果提取失败(例如输入字母而需要数字),则值不变且将设置 failbit (C++11 之前)。

如果提取失败,0 值被写入到变量中并设置 failbit;如果提取结果超出目标变量的最大或最小范围,std::numeric_limits::max() 或 std::numeric_limits::min() 被写入,并设置 failbit 标志位。(C++11 开始

看起来你的编译器正在使用 C++11 模式编译,这会改变行为。


输入运算符使用了区域设置 facet std::num_get,其 get 函数调用 do_get。对于 C++11,它指定使用类似 std::strtoll 的函数。在 C++11 之前,它显然使用类似 std::scanf 风格的解析(根据参考文献,我无法访问 C++03 规范)提取数字。行为变化是由于这种输入解析方式的改变造成的。


7
有趣的变化,我从未知道。我一直把在提取失败后读取变量视为未定义行为... - Kerrek SB
有人知道这个变化背后的原因吗? - Alex
@Alex:可能是为了使字符串转换函数的实现更容易(http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#1169)。不过,我找不到确切的提议文字更改的位置。 - Lightness Races in Orbit
阅读C++11规范,它实际上“使用”这些函数(§22.4.2.1.2,第3节,“stage” 3)。 - Some programmer dude
3
这个答案没有讲述完整的情况:如果构建哨兵对象失败,那么我们就不会到达“如果提取失败,则将零写入值”的步骤。这可能发生在流已经设置了错误条件或者只包含空格的情况下。(哨兵执行跳过空白字符的操作)。在这种情况下,原始值将被保留。 - M.M
显示剩余3条评论

4

运算符 >> 是一个格式化输入运算符。因此,它依赖于区域设置来确定如何从流中读取输入:

[istream.formatted.arithmetic]

与插入器一样,这些提取器依赖于语言环境的 num_get<> (22.4.2.1) 对象来执行解析输入流数据的操作。这些提取器表现为格式化输入函数(如 27.7.2.2.1 中所述)。构造出 sentry 对象后,转换的过程就像以下代码片段所执行的那样:

   typedef num_get< charT,istreambuf_iterator<charT,traits> > numget;
   iostate err = iostate::goodbit;
   use_facet< numget >(loc).get(*this, 0, *this, err, val);
   setstate(err);

如上所示,该值实际上是由嵌入到流中的numget facet设置的。

num_get虚函数[facet.num.get.virtuals]

第三阶段:

要存储的数字值可以是以下之一:

  • 如果转换函数无法将整个字段转换,则为零。将ios_base :: failbit分配给err。
  • 如果该字段表示一个太大正数以至于不能在val中表示,则为最大正可表示值。将ios_base :: failbit分配给err。
  • 如果该字段表示一个太大负数以至于不能在val中表示,则为最小可表示值或无符号整数类型的零。将ios_base :: failbit分配给err。

第三阶段的定义在n2723-> n2798之间发生了巨大变化

我在哪里找到当前的C或C ++标准文档?

num_get虚函数[facet.num.get.virtuals]

第三阶段:第二阶段处理的结果可能是以下之一:

  • 已在第二阶段累积了一系列字符,这些字符被转换(根据scanf的规则)为val类型的值。将此值存储在val中,并将ios_base :: goodbit存储在err中。
  • 在第二阶段累积的字符序列将导致scanf报告输入失败。将ios_base :: failbit分配给err。

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