【问题标题】:Is this proper behavior? std::map iterator invalidation这是正确的行为吗? std::map 迭代器失效
【发布时间】:2016-02-26 03:56:01
【问题描述】:
#include <iostream>
#include <map>

int main(int argc, char** argv)
{
  std::map<int, int> map;
  map.emplace(1, 1);
  auto reverse_iter = map.rbegin();
  std::cout << reverse_iter->first << ", " << reverse_iter->second << std::endl;
  map.emplace(2, 2);
  std::cout << reverse_iter->first << ", " << reverse_iter->second << std::endl;

  return 0;
}

打印出来:

1, 1
2, 2

根据标准,这真的应该发生吗?我没有接触 reverse_iter,但它指向的值正在改变。我认为 std::map 中的迭代器应该可以安全地防止插入。然而,它似乎决定 reverse_iter 不是一直指向我告诉它的值,而是“此时此刻地图末端发生的任何事情”。

更新:更多信息,以防万一:前向迭代器似乎不会发生这种情况(在我似乎能找到的任何情况下),我的 gcc 版本是 5.1.1-4。

【问题讨论】:

  • 被“无效”和指向新数据不是一回事,是吗?它是指向容器中最后一对的有效指针。那对恰好发生了变化。
  • 前向迭代器不会发生。将 rbegin 替换为 begin 并将 (2, 2) 替换为 (0, 0)。再说一次,这是否符合标准?因为它真的出乎意料,这是肯定的。
  • 对,你放在地图上的第一件事不会改变。但是你做的最后一件事会。
  • 地图中的一切都没有改变——迭代器指向的东西正在改变,而我没有接触迭代器。为什么?符合那个标准吗?迭代器被设计为像一个指针 - 你将它指向某个东西,它会一直指向那里并保持有效,直到你将它指向其他东西或者它变得无效。事实上,前向迭代器似乎就是这样。但不能逆转。这有什么意义?在任何类型的非常量上下文中使用反向迭代器都是一个大问题。
  • 没有迭代器失效;您的reverse_iterator 包含的常规iteratorend() 迭代器,即使在emplace 之后仍然有效。当您取消引用 reverse_iterator 时,它会递减 end() 迭代器并取消引用结果,从而为您提供您看到的输出。

标签: c++ iterator stdmap


【解决方案1】:

根据 C++ 标准(23.2.4 关联容器)

9 insert 和 emplace 成员不影响 迭代器和对容器的引用,擦除成员应 仅使迭代器和对已擦除元素的引用无效。

另一方面(24.5.1 反向迭代器)

1 类模板 reverse_iterator 是一个迭代器适配器,用于迭代 从由其底层迭代器定义的序列的末尾到 该序列的开头。

虽然在最后的引文中提到了std::reverse_iterator 类,但同样适用于标准容器的反向迭代器。

根据表 97 — 可逆容器要求

rbegin()对应reverse_iterator(end())

所以在您的示例中,反向迭代器仍然对应于end()。 `

【讨论】:

    【解决方案2】:

    这里引用http://en.cppreference.com/w/cpp/container/map/rbegin

    反向迭代器将迭代器存储到下一个元素而不是它实际引用的元素

    副作用是,如果您在该迭代器之前插入一些内容(包括 end()),您将在取消引用该反向迭代器时看到该新值。在这种情况下,我认为反向迭代器不会失效。

    【讨论】:

    • 所以,基本上,它是被设计破坏的? :Þ 更改您正在使用的迭代器旁边的内容可以更改您正在使用的迭代器吗?太好了...:Þ 但是,如果标准是这样说的,我无能为力。
    • 我认为您混淆了“有效”和“一致”。无效是一个非常具体的事情,这意味着不应该使用迭代器,因为它会导致未定义的行为。您对迭代器指向的内容在操作地图后不会改变的期望没有得到满足,但这不是标准的问题。
    • 鉴于迭代器的全部目的是作为指针和索引的替代品,我想说的是,当你修改它们没有指向的东西时,让它们发生变化的标准很破。我还要说,正向和反向迭代器之间的行为不一致也很糟糕。但事实就是如此。
    【解决方案3】:

    map.rbegin() 返回一个等于 std::reverse_iterator(map.end()); 的迭代器

    当您取消引用反向迭代器时会出现问题。当您取消引用reverse_iterator 时,您实际获得的值来自迭代器,然后将其存储在reverse_iterator 中。这可能看起来很奇怪,但这是有充分理由的,而且这是不可避免的。就是这样,为了安排一个范围的最后一个元素:一个迭代器指向一个范围内的一个过去的元素,当反转时,它指向最后一个元素(不是过去的)范围(这将是反转范围的第一个元素)。如果一个范围内第一个元素的迭代器被反转,反转的迭代器指向第一个元素之前的元素(这将是反转范围的结束元素)。

    也就是说,在您的情况下,取消引用 reverse_iter 等同于做:

    *(--map.end());
    

    因此,在第二个 emplace 之后,地图的最后一个元素发生了变化并取消引用 (--map.end())(即您的 reverse_iter),您将在地图中获得新的最后一个元素。

    【讨论】:

      【解决方案4】:
      std::map<int, int> map2;
      map2.emplace(2, 2);
      auto fiter = map2.begin();
      std::cout << fiter->first << ", " << fiter->second << std::endl;
      map2.emplace(1, 1);
      std::cout << fiter->first << ", " << fiter->second << std::endl;
      fiter = map2.begin();
      std::cout << fiter->first << ", " << fiter->second << std::endl;
      

      打印

      2, 2
      2, 2
      1, 1
      

      【讨论】:

        猜你喜欢
        • 2018-04-07
        • 1970-01-01
        • 2011-04-14
        • 2017-08-25
        • 1970-01-01
        • 1970-01-01
        • 2022-01-14
        • 2012-03-04
        • 1970-01-01
        相关资源
        最近更新 更多