【问题标题】:Does inserting/erasing an element from a std::map modify the iteration sequence?从 std::map 插入/删除元素会修改迭代序列吗?
【发布时间】:2013-05-25 11:52:10
【问题描述】:

假设我有以下代码:

typedef std::map< int, std::string >::iterator Iterator;
Iterator iter = myMap.begin();

while (iter != myMap.end())
{
    Iterator current = iter;
    ++iter;

    maybeDeleteElement( current ) // may call erase.
}

鉴于std::map 被实现为红黑树,是否保证地图中的每个元素都将被访问一次?或者修改映射会导致树重新平衡,从而改变迭代顺序?

注意:这不是关于任何迭代器是否会失效的问题。但是保持有效的迭代器并不一定意味着递增它会给您提供与之前相同的下一个元素。

【问题讨论】:

  • Iterator invalidation rules 的可能重复项
  • 编辑后,这不再是上面建议的问题的重复。
  • 您的示例和您标题中的问题仅略有相关。你是在问标题中的问题,还是在问你的例子?
  • 一个典型的红黑树std::map的迭代器本质上是一个指向包含某个元素的节点的指针,而不是像“从左到右的根节点”。因此,当树重新平衡时,未失效的迭代器继续指向同一元素。不过,我很难找到标准报价来证明这一切都是有保证的。

标签: c++ iterator stdmap red-black-tree


【解决方案1】:

std::map 中,将按顺序访问元素。

如果您存储的迭代器引用的元素未删除,因此未失效,则迭代器仍将引用同一元素。 (如果它是结束迭代器,它仍然是结束迭代器,因为它没有失效)。

当你推进那个迭代器时,它会在你引用的元素之后按顺序推进到下一个元素。

对于您的特定示例,是的,每个元素都将被访问一次,因为所有元素的删除都是在循环的当前迭代器状态之前的元素。

如果您在用于迭代的任何迭代器之前插入元素,那么当您使用迭代器向前迭代时,您最终会到达它们。如果您在用于迭代的任何迭代器之前删除元素,那么如果您使用该迭代器进行迭代,它们将不再是您将到达的未来元素的一部分。

如果您插入或删除迭代器当前位置之前的元素,除非您开始调用 -- 或类似函数,否则您当前的迭代将继续进行,而不会注意到它们已消失。

这是因为++ 在有序容器中的有效迭代器上保证返回顺序中的下一个元素,并且不会使迭代器无效的其他迭代器上的操作不会更改该迭代器的不变量(比如什么元素他们指的是)。

【讨论】:

    【解决方案2】:

    是的,插入/擦除可以修改迭代顺序。在您的示例中不会发生这种情况,因为您擦除已经通过的迭代器,但是如果您擦除/插入位于当前迭代器 ahead 的元素,那么它将修改其余的顺序。

    这是一个显示此类行为的短代码:

    int main (){
        map<int,int> mapa;
        for(int i = 0; i < 5; ++i) mapa[i] = i;
        bool add = false;
        for(auto it = mapa.begin(); it != mapa.end(); ++it){
            int x = it->second;
            printf("%d\n", x);
            if(add) mapa.erase(x+1);
            add = !add;
        }
        return 0;
    }
    

    上面的示例将打印0 1 3 4(而不是0 1 2 3 4)。此外,如果您擦除当前迭代器,它对下一个元素的引用将失效,并且您的程序将在下一次迭代时崩溃。

    或者,您也可以测试插入,方法是将上面的 if(add) 替换为:

    if(add) mapa[x+5] = x+5;
    else mapa[x-20] = x-20;
    

    该示例将打印额外的元素 {6, 8, 11, 16},而不打印负数,因为它们被插入到当前元素之前的位置。

    【讨论】:

    • 您的示例与 OP 的示例不同。 OP 的示例在元素可能被删除之前使迭代器前进。在迭代器前进之前,您删除了一个元素。
    • 没错,但问题是指插入/擦除元素,而不仅仅是示例。在 OP 的代码中它会运行得很好,因为过去的元素不会影响未来元素的顺序,就像我的插入示例演示的那样。
    • @Yakk 我专门讨论了在擦除之前推进迭代器,但是在这里提到这个警告仍然很有用。
    【解决方案3】:

    map 中删除元素不会使迭代器无效,因此我希望它能够继续正确迭代。

    https://stackoverflow.com/a/6438087/5987

    【讨论】:

    • 这真的取决于 std::map 迭代器是如何实现的:它是否使用树的结构,或者它是否使用键的顺序(它似乎这样做)。跨度>
    • @DominicGurto,不,它不依赖于实现 - 如果标准保证它,实现别无选择,只能遵守。
    • 如果标准没有规定,那么实现可以为所欲为……
    • @DominicGurto,如果不清楚,那是我的观点 - 在这种情况下,标准 确实 指定。
    【解决方案4】:

    是的,迭代顺序发生了变化。这是由于 §23.2.4.1/10 和 /11:

    (p10) 关联容器迭代器的基本特性是它们以键的非降序遍历容器,其中非降序由用于构造它们的比较定义。对于任意两个可解引用的迭代器 i 和 j 使得 i 到 j 的距离为正,

    value_comp(*j, *i) == false
    

    (p11) 对于具有唯一键的关联容器,更强的条件成立,

    value_comp(*i, *j) != false.
    

    如果在插入之后,无论元素的顺序如何,在迭代序列的开头都添加了新元素(以免在任何现有迭代器位置之前修改序列),则将违反上述要求.

    【讨论】:

    • 我认为你所说的“迭代序列改变”的意思与OP询问迭代序列是否改变时的想法完全不同。因此,您的回答会令人困惑,而且似乎自相矛盾。
    • @BenjaminLindley 上面的意思是,当你推进一个迭代器时,你会按顺序获得下一个元素(由value_comp定义)。因此,如果您进行插入并 then 推进迭代器,您将获得的元素可能与没有插入时不同。这不是OP想知道的吗?对不起,如果我误解了。
    • 在他的代码之后直接阅读 OP 的段落。请注意,有 2 个命题,OP 认为是反对的。第一个提议似乎很清楚。 “是否保证地图中的每个元素都会被访问一次?” -- 答案显然是肯定的。第二个命题,“或者修改映射会导致......迭代顺序发生变化?” -- 不清楚 OP 的意思是什么,但如果它与第一个命题相反,那么它一定是假的。我并不是说你的回答是错误的,但我认为这个措辞令人困惑。
    【解决方案5】:

    尽管您有注释,但对于erase 的情况,这个问题完全是关于迭代器失效的,因为如果没有迭代器失效,则顺序必然保持不变。这是因为map是一个排序容器,所以无论内部表示可能发生什么变化,迭代顺序都必须保持完全相同。

    为了具体解决您的示例,它将只遍历每个元素一次,因为您保存了迭代器以检查和递增您的遍历迭代器。

    在插入的情况下,如果您在当前迭代点之前插入,则不会访问该元素。在当前迭代器之后插入将导致遍历新项目。

    【讨论】:

      猜你喜欢
      • 2014-01-31
      • 2012-04-01
      • 2011-06-03
      • 2011-02-21
      • 2021-12-21
      • 2023-04-09
      • 2010-10-10
      相关资源
      最近更新 更多