【问题标题】:Erase/Remove contents from the map (or any other STL container) while iterating it迭代时从地图(或任何其他 STL 容器)中擦除/删除内容
【发布时间】:2010-11-05 12:56:59
【问题描述】:

据称,当迭代器变得无效时,您不能在迭代时擦除/删除容器中的元素。删除满足特定条件的元素的(安全)方法是什么?请只使用 stl,不要使用 boost 或 tr1。

编辑 如果我想删除一些符合特定标准的元素,是否有更优雅的方法,也许使用 functor 和 for_each 或擦除算法?

【问题讨论】:

    标签: c++ stl


    【解决方案1】:
    template <class Container, class Predicate>
    void eraseIf( Container& container, Predicate predicate  ) {
        container.erase( remove_if( container.begin(), container.end(), predicate ), container.end() );
    }   
    
    // pre-c++11 version
    template<class K, class V, class Predicate> 
    void eraseIf( std::map<K,V>& container, Predicate predicate) {
        typename std::map<K,V>::iterator iter = container.begin();
        while(iter!=container.end()) { 
            iterator current = iter++;
            if(predicate(*current))
                container.erase(current);
        }
    }
    
    // c++11 version
    template<class K, class V, class Predicate> 
    void eraseIf( std::map<K,V>& container, Predicate predicate) {
        auto iter = container.begin();
        while(iter!=container.end()) {
            if(predicate(*iter))
                iter = container.erase(iter);
            else
                ++iter;
        }
    }
    

    【讨论】:

    • 地图版本在迭代器被擦除后尝试增加迭代器(因此无效)。
    • 对于地图版本,您需要这个 if(cond) it = c.erase(it); else ++it; .. 并且不要按照惯例在 for 中增加 it
    • 原始代码早于 C++11 并且在过去,擦除返回类型是无效的。据我所知,迭代器不能被取消引用,但仍然可以被推进。
    【解决方案2】:

    1.对于std::vector&lt;&gt;

    std::vector <int> vec;
    vec.erase(std::remove(vec.begin(),vec.end(), elem_to_remove), vec.end());
    

    2.对于std::map&lt;&gt;,请始终使用std::map::erase()

    std::map<int,std::string> myMap;
    myMap.emplace(std::make_pair(1, "Hello"));
    myMap.emplace(std::make_pair(2, "Hi"));
    myMap.emplace(std::make_pair(3, "How"));
    myMap.erase( 1);//Erase with key
    myMap.erase(myMap.begin(), ++myMap.begin() );//Erase with range
    for( auto &ele: myMap)
    {
        if(ele.first ==1)
        {
            myMap.erase(ele.first);//erase by key 
            break; //You can't use ele again properly 
                   //wthin this iteration, so break.
        }
    }
    
    1. 对于std::list 使用std::list::erase()

    【讨论】:

      【解决方案3】:

      使用后减运算符返回迭代器的副本它减量的事实。由于在擦除当前元素后递减的迭代器仍然有效,因此 for 循环继续按预期运行。

      #include <list>
      std::list<int> myList;
      for(int i = 0; i < 10; ++i )
      {
         myList.push_back(i);
      }
      
      int cnt = 0;
      for(std::list<int>::iterator iter = myList.begin(); iter != myList.end(); ++iter)
      {
         if( cnt == 5 )
         {
            myList.erase(iter--);
         }
         ++cnt;
      }
      

      编辑:如果您尝试擦除列表中的第一个元素,则不起作用....

      【讨论】:

        【解决方案4】:

        Viktor 的解决方案的好处是可以在移除之前对元素做一些事情。 (我无法使用 remove_ifremove_copy_if 做到这一点。)但我更喜欢使用 std::find_if,所以我不必自己增加迭代器:

        typedef vector<int> int_vector;
        int_vector v;
        
        int_vector::iterator itr = v.begin();
        for(;;)
        {
            itr = std::find_if(itr, v.end(), Predicate(4));
            if (itr == v.end())
            {
                break;
            }
        
            // do stuff with *itr here
        
            itr = v.erase(itr);  // grab a new, valid iterator
        }
        

        谓词可以是bind1st( equal_to&lt;int&gt;(), 4 ) 或类似的东西:

        struct Predicate : public unary_function<int, bool>
        {
            int mExpected;
            Predicate(int desired) : mExpected(desired) {}
            bool operator() (int input)
            {
                return ( input == mExpected );
            }
        };
        

        【讨论】:

          【解决方案5】:

          markh44 是最接近 STL 的响应。 但是请注意,一般情况下,通过修改容器会使迭代器失效,但 set 和 map 是例外。在那里,您可以删除项目并继续使用迭代器,除非您删除了迭代器正在引用的项目。

          【讨论】:

            【解决方案6】:

            我更喜欢带有while的版本:

            typedef std::list<some_class_t> list_t;
            void f( void ) {
              // Remove items from list
              list_t::iterator it = sample_list.begin();
              while ( it != sample_list.end() ) {
                if ( it->condition == true ) {
                  it = sample_list.erase( it );
                } else ++it;    
              }
            }
            

            使用while,将it 增加两倍于for 循环中是没有危险的。

            【讨论】:

              【解决方案7】:
              bool IsOdd( int i )
              {
                  return (i&1)!=0;
              }
              
              int a[] = {1,2,3,4,5};
              vector<int> v( a, a + 5 );
              v.erase( remove_if( v.begin(), v.end(), bind1st( equal_to<int>(), 4 ) ), v.end() );
              // v contains {1,2,3,5}
              v.erase( remove_if( v.begin(), v.end(), IsOdd ), v.end() );
              // v contains {2}
              

              【讨论】:

              • bind1st 创建了一个类似对象的函数,该对象本质上为您提供了一个带有常量第一个参数的函数调用 - 因此在示例中,它将具有 equal_to(4, X) 的效果,其中 X 来自我们正在迭代的序列。效果是序列中的每个值都与4进行比较。
              【解决方案8】:

              只要在删除迭代器后不使其失效,就可以:

              MyContainer::iterator it = myContainer.begin();
              while(it != myContainer.end())
              {
                  if (*it == matchingValue)
                  {
                     myContainer.erase(it++);
                  }
                  else
                  {
                      ++it;
                  }
              }
              

              【讨论】:

              • +1。 “myContainer.erase(it++);”是微妙的——它在调用erase()之前正确执行增量,当它仍然有效时,同时将unincremented迭代器(的副本)传递给那个功能。
              • 重要提示:该代码适用于 map、set 和 list,但不适用于向量——从向量中删除会使迭代器和所有后续元素 (23.2.4.3/3) 无效。暂时放弃了我的 +1,当你提到这一点时会重新 +1。
              • @Ismael:后增量在递增之前返回其操作数的未修改副本。 STL 迭代器保证了这一点。
              • 后增量发生在调用 erase() 之前,因为调用需要该值。 Erase() 获取未递增指针的副本。
              • @Ismael:函数调用是序列点,因此增量的副作用保证在调用擦除开始之前完成。
              【解决方案9】:

              std::vector 示例

              #include <vector>
              
              using namespace std;
              
              int main()
              {
              
                 typedef vector <int> int_vector;
              
                 int_vector v(10);
              
                 // Fill as: 0,1,2,0,1,2 etc
                 for (size_t i = 0; i < v.size(); ++i){
                    v[i] = i % 3;
                 }
              
                 // Remove every element where value == 1    
                 for (int_vector::iterator it = v.begin(); it != v.end(); /* BLANK */){
                    if (*it == 1){
                       it = v.erase(it);
                    } else {
                       ++it;
                    }
                 }
              
              }
              

              【讨论】:

              • 不知道,但从擦除返回的迭代器不是一个新的、经过验证的迭代器吗?听起来很奇怪,它会返回一个无效的迭代器?
              • @j_random_hacker:你是正确的,它使任何迭代器无效..但是 std::vector::erase 返回一个 new, valid 迭代器擦除后的元素(或结束)。此代码完全有效。
              • 对不起,你说得对——一定是误读了你的代码。擦除返回的迭代器指向下一个元素 (23.1.1/7)。 +1。
              • 很好的例子,但是为了为 map(及其朋友)工作,你不能使用 erase() 的返回值——由于某种原因 std::map::erase() 返回void(MS 会在这个问题上搞砸你的脑袋)
              • @j_random_hacker: 但是 remove()\remove_if() 也只适用于序列?
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2013-04-07
              • 2012-03-28
              • 1970-01-01
              • 2013-02-09
              • 1970-01-01
              • 1970-01-01
              • 2019-01-20
              相关资源
              最近更新 更多