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

Dan*_*ani 160 c++ map c++11

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

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

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

Ker*_* SB 261

标准的关联容器擦除习语:

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;
  }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们真的想要一个普通的for循环,因为我们正在修改容器本身.基于范围的循环应严格保留用于我们只关心元素的情况.RBFL的语法通过甚至不暴露循环体内的容器来明确这一点.

编辑.在C++ 11之前,您无法擦除常量迭代器.你不得不说:

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

从容器中删除元素与元素的常量不一致.通过类比,它一直是完全合法delete p这里p是一个指针到常量.Constness不会限制生命周期; C++中的const值仍然可以停止存在.

  • 我在某处看到,在C++ 11中,`it = v.erase(it);`现在也适用于地图.也就是说,*all*associative元素上的erase()现在返回下一个迭代器.因此,不再需要在delete()中需要后增量++的旧kludge.这个(如果是真的)是一件好事,因为kludge依赖于覆盖 - 后增量 - 函数 - 调用魔法,由新手维护者"修复"以从函数调用中取出增量,或者交换它预先增量"因为那只是一种风格",等等. (5认同)
  • @skyhisi:的确如此.这是后增量的合法用法之一:*首先*增加`it`以获得下一个有效的迭代器,然后*然后*擦除旧的.它反过来不起作用! (3认同)
  • 为什么你会在`if` _and_`sets`块中调用`it ++`?这些在_after_之后调用它不够吗? (3认同)
  • @Dani:嗯,这与20世纪的建筑`for(int i = 0; i <v.size(); i ++)`形成鲜明对比.这里我们不得不在循环中说'v [i]`,即我们必须明确提到容器.另一方面,RBFL引入了可直接用作值的循环变量,因此在循环内不需要容器知识.这是RBFL用于循环的预期用途的线索,这些循环不需要知道容器.擦除是完全相反的情况,它是关于容器的全部. (2认同)
  • @Ewat:不,没关系。这是*擦除*使擦除的迭代器无效;增量发生在擦除之前。这是使用后增量的典型示例。 (2认同)

D C*_*zee 22

我个人更喜欢这种模式,它更加清晰和简单,代价是额外的变量:

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

这种方法的优点:

  • for循环增量器作为增量器是有意义的;
  • 擦除操作是简单的擦除,而不是与增量逻辑混合;
  • 在循环体的第一行之后,整个迭代中的含义itnext_it保持固定,允许您轻松添加引用它们的其他语句,而不必头脑确定它们是否会按预期工作(当然除了it擦除后不能使用) .

  • 我实际上可以想到另一个优点,如果循环调用的代码删除了被迭代的条目或先前的条目(并且循环没有意识到),它将在不造成任何损害的情况下工作。唯一的限制是某些东西是否正在擦除next_it或后继者指向的对象。也可以对完全清除的列表/地图进行测试。 (2认同)
  • 请记住使用此答案的任何人:您必须在 for 循环中使用“++next_it”,而不是在迭代表达式中。如果您尝试将其移动到迭代表达式中为“it = next_it++”,那么在最后一次迭代中,当“it”将被设置为等于“m.cend()”时,您将尝试迭代“next_it”过去的“m.cend()”,这是错误的。 (2认同)

Mat*_*att 8

假设 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);
Run Code Online (Sandbox Code Playgroud)

其他一些小的样式更改:

  • Map::const_iterator在可能/方便时显示声明的类型 ( ),而不是使用auto.
  • 使用using模板类型,使辅助类型(Map::const_iterator)更易于阅读/维护。


Pet*_*erT 7

C++20 草案包含便利功能std::erase_if.

因此,您可以使用该函数将其作为单行代码来完成。

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);});
Run Code Online (Sandbox Code Playgroud)


Mic*_*aum 5

很伤心吧?我通常的做法是构建一个迭代器容器,而不是在遍历期间删除。然后循环遍历容器并使用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)
}
Run Code Online (Sandbox Code Playgroud)

  • 不:http://stackoverflow.com/questions/263945/what-happens-if-you-call-erase-on-a-map-element-while-iteating-from-begin-to (4认同)

Kas*_*yap 5

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

  • 使用旧地图暗示:您不能
  • 使用新的地图隐含功能:几乎像@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
Run Code Online (Sandbox Code Playgroud)

新旧样式的示例:

#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;
}
Run Code Online (Sandbox Code Playgroud)

印刷品:

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.
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

97860 次

最近记录:

6 年,11 月 前