简体   繁体   English

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

[英]How to remove from a map while iterating it?

How do I remove from a map while iterating it?迭代时如何从地图中删除? like:喜欢:

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

If I use map.erase it will invalidate the iterators如果我使用map.erase它会使迭代器失效

The standard associative-container erase idiom:标准的关联容器擦除习惯用法:

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;
  }
}

Note that we really want an ordinary for loop here, since we are modifying the container itself.请注意,我们真的想要一个普通的for循环,因为我们正在修改容器本身。 The range-based loop should be strictly reserved for situations where we only care about the elements.基于范围的循环应该严格保留用于我们只关心元素的情况。 The syntax for the RBFL makes this clear by not even exposing the container inside the loop body. RBFL 的语法通过甚至不暴露循环体内的容器来清楚地说明这一点。

Edit.编辑。 Pre-C++11, you could not erase const-iterators.在 C++11 之前,您无法擦除常量迭代器。 There you would have to say:在那里你必须说:

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

Erasing an element from a container is not at odds with constness of the element.从容器中擦除元素与元素的常量性并不矛盾。 By analogy, it has always been perfectly legitimate to delete p where p is a pointer-to-constant.以此类推, delete p一直是完全合法的,其中p是指向常量的指针。 Constness does not constrain lifetime;稳定性不限制寿命; const values in C++ can still stop existing. C++ 中的 const 值仍然可以停止存在。

I personally prefer this pattern which is slightly clearer and simpler, at the expense of an extra variable:我个人更喜欢这种稍微清晰和简单的模式,但需要额外的变量:

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

Advantages of this approach:这种方法的优点:

  • the for loop incrementor makes sense as an incrementor; for 循环增量器作为增量器是有意义的;
  • the erase operation is a simple erase, rather than being mixed in with increment logic;擦除操作是简单的擦除,而不是与增量逻辑混在一起;
  • after the first line of the loop body, the meaning of it and next_it remain fixed throughout the iteration, allowing you to easily add additional statements referring to them without headscratching over whether they will work as intended (except of course that you cannot use it after erasing it).在循环体的第一行之后, itnext_it的含义在整个迭代next_it保持不变,允许您轻松添加引用它们的其他语句,而无需担心它们是否会按预期工作(当然,您不能在之后使用it擦除它)。

Assuming C++11, here is a one-liner loop body, if this is consistent with your programming style:假设 C++11,这是一个单行循环体,如果这与您的编程风格一致:

using Map = std::map<K,V>;
Map map;

// Erase members that satisfy needs_removing(itr)
for (Map::const_iterator itr = map.cbegin() ; itr != map.cend() ; )
  itr = needs_removing(itr) ? map.erase(itr) : std::next(itr);

A couple of other minor style changes:其他一些小的样式更改:

  • Show declared type ( Map::const_iterator ) when possible/convenient, over using auto .在可能/方便时显示声明的类型( Map::const_iterator ),而不是使用auto
  • Use using for template types, to make ancillary types ( Map::const_iterator ) easier to read/maintain.使用using模板类型,使辅助类型( Map::const_iterator )更易于阅读/维护。

In short "How do I remove from a map while iterating it?"简而言之“如何在迭代地图时从地图中删除它?”

  • With old map impl: You can't使用旧地图实现:你不能
  • With new map impl: almost as @KerrekSB suggested.使用新地图实现:几乎就像@KerrekSB 建议的那样。 But there are some syntax issues in what he posted.但是他发布的内容存在一些语法问题。

From GCC map impl (note GXX_EXPERIMENTAL_CXX0X ):来自 GCC 地图 impl(注意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

Example with old and new style:新旧样式示例:

#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;
}

prints:印刷:

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.

The C++20 draft contains the convenience function std::erase_if . C++20 草案包含便利函数std::erase_if

So you can use that function to do it as a one-liner.因此,您可以使用该函数将其作为单行代码来完成。

std::map<K, V> map_obj;
//calls needs_removing for each element and erases it, if true was reuturned
std::erase_if(map_obj,needs_removing);
//if you need to pass only part of the key/value pair
std::erase_if(map_obj,[](auto& kv){return needs_removing(kv.first);});

Pretty sad, eh?很伤心吧? The way I usually do it is build up a container of iterators instead of deleting during traversal.我通常这样做的方式是建立一个迭代器容器,而不是在遍历过程中删除。 Then loop through the container and use map.erase()然后循环遍历容器并使用 map.erase()

std::map<K,V> map;
std::list< std::map<K,V>::iterator > iteratorList;

for(auto i : map ){
    if ( needs_removing(i)){
        iteratorList.push_back(i);
    }
}
for(auto i : iteratorList){
    map.erase(*i)
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM