【问题标题】:For input iterators, why a == b does not imply ++a == ++b?对于输入迭代器,为什么 a == b 并不意味着 ++a == ++b?
【发布时间】:2011-05-10 09:01:54
【问题描述】:

§24.1.1/3 从 C++03 标准读取,

对于输入迭代器,a == b 并不意味着 ++a == ++b。不等于 保证替代财产或 参考透明度。) 算法 输入迭代器永远不应该 尝试通过相同的 迭代器两次。他们应该是单身 通过算法。值类型 T 不是 必须是 Assignable 类型 (23.1)。可以使用这些算法 以 istreams 作为源 通过输入数据 istream_iterator 类。

我无法理解上述引文中的粗体字。谁能帮我理解这个?

另外,以下陈述(上述引文中的斜体文字)是什么意思?它与a==b++a==++b 表达式有什么关系?

平等不 保证替代财产或 参考透明度。

【问题讨论】:

    标签: c++ stl iterator


    【解决方案1】:

    对于输入迭代器,递增迭代器会使同一迭代器的副本无效。

    所以:

    auto a = istream_iterator<whatever>(something);
    auto b = a;
    a == b; // true
    ++a;    // b is now invalid
    ++b;    // undefined behavior, I think, but in any case not guaranteed to
            // result in anything sensible.
    

    所以++a == ++b 当然不能保证。也就是说,a == b 并不意味着 ++a == ++b

    我认为“替换属性”的意思是“你用a 的值做的任何事情都与用b 的值做同样的结果”,或类似的——它有各种版本的替换可能指的是,但类似的东西。我认为在这种情况下,它必须意味着“稍后b做同样的事情”,因为如果a == b并且我还没有做任何无效的事情,那么@中的哪一个都没有关系我使用 987654329@ 和 b,它们指的是流中的同一点。但是当我增加时,我必须选择一个而失去另一个,因此++a == ++b 的困难。

    “参照透明”意味着不同的对象是独立的,即它们不是彼此的引用/指针或别名。结合“替代属性”,这意味着:

    稍后?没有较早稍后,因为操作没有全局副作用。如果你不能 替换“稍后”然后你 无法替代

    相同序列中的输入迭代器通常引用相同的“实际数据”,例如文件句柄或其他本身包含可变状态的东西。由于ab 引用同一个文件句柄,并且它们的值取决于它的状态,因此您没有引用透明性。这种缺乏是为什么替换失败的原因。

    前向迭代器通常引用相同的底层数据(如容器),但只要您以只读方式使用它们(并且不要以其他方式修改容器),它们就不会'不要背叛这个事实,至少在你开始比较它们返回的值的地址之前是这样。因此,它们具有有限的引用透明度它们自己的值,而输入迭代器则没有。它们本身仍然是引用,所以它们引用的东西仍然是别名。

    【讨论】:

    • 虽然是真的,但这真的是该段落所具体谈论的吗?
    • @Steve:这并不能解释我的问题。另外,这段代码在 C++03 中有效吗?
    • @Tomalak:我相信是的。该段落讨论的是输入迭代器仅适用于单次传递,对我来说,“增量使副本无效”是表达这一点的最简单方法。它还简单地解释了为什么a == b 不暗示++a == ++b(因为它提供了一个反例)。毫无疑问,还有其他可能的解释,其中一些也很简单:-)
    • @Nawaz:不,我正在使用auto,因为我懒得复制类型甚至编写 typedef。如果不那么懒惰是美德,请随时通过编辑我的代码在天堂赢得一席之地!
    • @Nawaz:除了你的第一个问题,我现在还试图回答你的第二个问题。
    【解决方案2】:

    正如解释所说:“它们应该是单通道算法。”

    关键是输入流上的迭代器代表一个瞬态。一旦迭代器改变了,那个状态就不再存在了;表示该状态的所有其他迭代器都将失效:一旦增加 a,迭代器 b 就会失效。

    【讨论】:

      【解决方案3】:

      所指的属性是:

      替换属性

      对于任何数量 a 和 b 以及任何表达式 F(x),如果 a = b,则 F(a) = F(b)(如果任一边都有意义,即格式良好)。

      Referential Transparency

      通俗地说,这意味着值和对该值的引用之间没有区别(因此创造了这个术语)。

      在命令式编程中,这是一个很难理解的概念,因为我们习惯于修改我们的变量。 Rick Hickey(在 Clojure 后面)对distinction between Identity and State 进行了精彩的演讲,这可能会对您有所帮助。它的要点是变量是一个身份。在任何时间点,身份都指的是状态。一个状态永远不会改变,但是一个身份可能会被改变以引用另一个状态。

      输入迭代器

      substitution property 违规在这里是“明显的”,如果我们在上面定义 F(x) 来表示 ++x,那么如果输入迭代器验证了替换属性,以下将保持a == b =&gt; ++a == ++b

      然而,这不是真的,因为增加输​​入迭代器可能会使来自同一源的所有其他输入迭代器无效。来自 n3290 中的表 107(第 831 页,就在您引用的段落上方):

      ++r

      pre: r 是可解引用的。

      post: r 是可取消引用的或 r 是过去的。

      post:不再要求 r 的先前值的任何副本 可解引用或在 == 的域中。

      也就是说,当我们执行++a时,那么b可能会失效,因此++b本身就会是未定义的行为。

      这直接违反了++a == ++b,因此替换属性不成立。

      参照透明度在这里更加明显。如果输入迭代器是引用透明的,这意味着它们与它们指向的值是不可区分的。显然情况并非如此,因为应用 ++ 不会增加值,而是增加迭代器。

      【讨论】:

      • 对了,我在wikipedia找到了一个有趣的解释:"Because of this they lack referential transparency, i.e. the same language expression can result in different values at different times depending on the state of the executing program."
      • @Nawaz:是的,一般来说,您需要纯度来实现表达式的引用透明性(即,不读取某个全局状态,也不写入全局状态)。例如,i = rand() 不能(也不应该)被记忆,而 i, RandState = rand(RandState) 可以,因为状态现在是明确的。
      • @MatthieuM。您能否详细说明That is, when we perform ++a, then b may become invalid, and therefore ++b itself will be undefined behavior. 我看不出b 可能会变得无效。
      • @newprint:想象一下,您在缓冲区的字节上使用迭代器。当你用ab 指向缓冲区的最后一个字节并执行++a 时,它可能会发出信号:“我已经完成了,带来下一个”,此时b 可能是悬空指针。
      【解决方案4】:

      输入迭代器定义了一个只能读取一次的序列;输入 从键盘或管道将是一个很好的例子。增加一个 istream_iterator 实际上意味着在istream 中进一步阅读, 提取字符,因此同一流上的其他 istream_iterator 就他们的立场而言将不再有效。想象 一个字符流"abcdefg..."(字母表,总和),有两个 istream_iterator&lt;char&gt; 指向'a'。递增其中之一 它们将导致从流中读取'b',而不是其他 迭代器可以看到它。

      【讨论】:

        【解决方案5】:

        假设输入迭代器可以连接到从键盘读取的流。递增迭代器意味着读取下一个字符。

        同样递增迭代器的副本并不意味着读取相同的字符。

        【讨论】:

          猜你喜欢
          • 2014-03-29
          • 2010-12-08
          • 1970-01-01
          • 2021-10-06
          • 2021-09-10
          • 2011-05-30
          • 1970-01-01
          • 1970-01-01
          • 2019-10-27
          相关资源
          最近更新 更多