如何在迭代时从地图中删除?


177

如何在迭代时从地图中删除?喜欢:

std::map<K, V> map;
for(auto i : map)
    if(needs_removing(i))
        // remove it from the map

如果我使用map.erase它将使迭代器无效





Answers:


279

标准的关联容器擦除习惯用法:

for (auto it = m.cbegin(); it != m.cend() /* not hoisted */; /* no increment */)
{
  if (must_delete)
  {
    m.erase(it++);    // or "it = m.erase(it)" since C++11
  }
  else
  {
    ++it;
  }
}

请注意for,由于要修改容器本身,因此我们确实希望在此处进行普通循环。基于范围的循环应严格保留给我们只关心元素的情况。RBFL的语法甚至没有将容器暴露在循环体内,从而使这一点很清楚。

编辑。在C ++ 11之前的版本中,您无法删除构造函数。在那里你不得不说:

for (std::map<K,V>::iterator it = m.begin(); it != m.end(); ) { /* ... */ }

从容器中删除元素与元素的恒定性并不矛盾。通过类比,它一直是完全合法delete p这里p是一个指针到常量。坚固性不会限制寿命。C ++中的const值仍然可以停止存在。


1
“甚至没有将容器暴露在循环体内”是什么意思?
丹妮

2
@Dani:好吧,这与20世纪的建筑形成对比for (int i = 0; i < v.size(); i++)。在这里v[i],我们必须在循环内说,即,我们必须明确提及容器。另一方面,RBFL引入了可直接用作值的循环变量,因此在循环内部不需要了解容器。这是一个线索RBFL的预期用途针对那些循环必须了解的容器。擦除是完全相反的情况,它与容器有关。
Kerrek SB 2011年

3
@skyhisi:的确如此。这是后增量的合法用途之一:首先进行增量it以获取下一个有效的迭代器,然后擦除旧的迭代器。相反,它不起作用!
Kerrek SB 2011年

5
我读过C ++ 11中的某个地方,it = v.erase(it);现在也适用于地图,也就是说,所有关联元素上的delete()现在都会返回下一个迭代器。因此,不再需要在delete()中需要post-increment ++的旧合并。这(如果为真)是一件好事,因为kludge依赖于在函数调用中重写后的增量,由新手维护人员“修复”以将增量从函数调用中移除或交换预先增加“因为这只是一件时尚的事情”,等等。–
Dewi Morgan

3
你为什么会叫it++if else块?这些之后再调用一次还不够吗?
nburk

24

我个人更喜欢这种模式,它更清晰,更简单,但会增加一个变量:

for (auto it = m.cbegin(), next_it = it; it != m.cend(); it = next_it)
{
  ++next_it;
  if (must_delete)
  {
    m.erase(it);
  }
}

这种方法的优点:

  • for循环增量器作为增量器是有意义的;
  • 擦除操作是简单的擦除,而不是与增量逻辑混合在一起;
  • 在循环体的第一行之后,it和的含义next_it在整个迭代过程中保持不变,从而使您可以轻松地添加引用它们的附加语句,而不必担心它们是否会按预期工作(当然,除非it删除后不能使用) 。

2
我实际上可以想到另一个优点,如果循环调用的代码删除了被迭代的条目或先前的条目(并且循环不知道该条目),它将在不造成任何损害的情况下工作。唯一的限制是某些东西是否正在擦除next_it或后继者指向的对象。也可以针对完全清除的列表/地图进行测试。
拉尔斯瓦德,2017年

即使循环更复杂并且具有多个逻辑级别来决定是否删除或执行其他各种任务,此答案也很简单明了。不过,我提出了一项修改,以使其变得更简单。可以在for的init中将“ next_it”设置为“ it”以避免错别字,并且由于init和迭代语句均将其和next_it设置为相同的值,因此您无需说“ next_it = it;”。在循环的开始。
cdgraham

1
请记住使用此答案的任何人:您必须在for循环中而不是在迭代表达式中包含“ ++ next_it”。如果您尝试将其作为“ it = next_it ++”移动到迭代表达式中,则在最后一次迭代中,当将“ it”设置为等于“ m.cend()”时,您将尝试迭代“ next_it”过去的“ m.cend()”,这是错误的。
cdgraham

6

简而言之,“如何在迭代时从地图中删除它?”

  • 使用旧地图暗示:您不能
  • 带有新的地图隐含内容:几乎与@KerrekSB建议的一样。但是他发布的内容中存在一些语法问题。

从GCC映射中显示(请注意GXX_EXPERIMENTAL_CXX0X):

#ifdef __GXX_EXPERIMENTAL_CXX0X__
      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // DR 130. Associative erase should return an iterator.
      /**
       *  @brief Erases an element from a %map.
       *  @param  position  An iterator pointing to the element to be erased.
       *  @return An iterator pointing to the element immediately following
       *          @a position prior to the element being erased. If no such 
       *          element exists, end() is returned.
       *
       *  This function erases an element, pointed to by the given
       *  iterator, from a %map.  Note that this function only erases
       *  the element, and that if the element is itself a pointer,
       *  the pointed-to memory is not touched in any way.  Managing
       *  the pointer is the user's responsibility.
       */
      iterator
      erase(iterator __position)
      { return _M_t.erase(__position); }
#else
      /**
       *  @brief Erases an element from a %map.
       *  @param  position  An iterator pointing to the element to be erased.
       *
       *  This function erases an element, pointed to by the given
       *  iterator, from a %map.  Note that this function only erases
       *  the element, and that if the element is itself a pointer,
       *  the pointed-to memory is not touched in any way.  Managing
       *  the pointer is the user's responsibility.
       */
      void
      erase(iterator __position)
      { _M_t.erase(__position); }
#endif

新旧样式示例:

#include <iostream>
#include <map>
#include <vector>
#include <algorithm>

using namespace std;
typedef map<int, int> t_myMap;
typedef vector<t_myMap::key_type>  t_myVec;

int main() {

    cout << "main() ENTRY" << endl;

    t_myMap mi;
    mi.insert(t_myMap::value_type(1,1));
    mi.insert(t_myMap::value_type(2,1));
    mi.insert(t_myMap::value_type(3,1));
    mi.insert(t_myMap::value_type(4,1));
    mi.insert(t_myMap::value_type(5,1));
    mi.insert(t_myMap::value_type(6,1));

    cout << "Init" << endl;
    for(t_myMap::const_iterator i = mi.begin(); i != mi.end(); i++)
        cout << '\t' << i->first << '-' << i->second << endl;

    t_myVec markedForDeath;

    for (t_myMap::const_iterator it = mi.begin(); it != mi.end() ; it++)
        if (it->first > 2 && it->first < 5)
            markedForDeath.push_back(it->first);

    for(size_t i = 0; i < markedForDeath.size(); i++)
        // old erase, returns void...
        mi.erase(markedForDeath[i]);

    cout << "after old style erase of 3 & 4.." << endl;
    for(t_myMap::const_iterator i = mi.begin(); i != mi.end(); i++)
        cout << '\t' << i->first << '-' << i->second << endl;

    for (auto it = mi.begin(); it != mi.end(); ) {
        if (it->first == 5)
            // new erase() that returns iter..
            it = mi.erase(it);
        else
            ++it;
    }

    cout << "after new style erase of 5" << endl;
    // new cend/cbegin and lambda..
    for_each(mi.cbegin(), mi.cend(), [](t_myMap::const_reference it){cout << '\t' << it.first << '-' << it.second << endl;});

    return 0;
}

印刷品:

main() ENTRY
Init
        1-1
        2-1
        3-1
        4-1
        5-1
        6-1
after old style erase of 3 & 4..
        1-1
        2-1
        5-1
        6-1
after new style erase of 5
        1-1
        2-1
        6-1

Process returned 0 (0x0)   execution time : 0.021 s
Press any key to continue.

1
我不明白 有什么问题mi.erase(it++);
lvella

1
@lvella见上文。“如果我使用map.erase,它将使迭代器无效”。
Kashyap '18

如果删除后地图变为空白,则您的新方法将不起作用。在这种情况下,迭代器将无效。因此,擦除后不久,最好插入if(mi.empty()) break;
Rahat Zaman
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.