Стереть / удалить содержимое с карты (или любого другого контейнера STL) во время итерации - PullRequest
26 голосов
/ 24 июня 2009

Предположительно, вы не можете просто стереть / удалить элемент в контейнере, пока итерация становится недействительной. Каковы (безопасные) способы удаления элементов, которые соответствуют определенному условию? пожалуйста, только STL, без повышения или TR1.

EDIT Есть ли более элегантный способ, если я хочу стереть несколько элементов, которые соответствуют определенным критериям, возможно, с использованием functor и for_each или алгоритма удаления?

Ответы [ 9 ]

33 голосов
/ 24 июня 2009

Вы можете до тех пор, пока вы не аннулируете свой итератор после того, как вы его удалили:

MyContainer::iterator it = myContainer.begin();
while(it != myContainer.end())
{
    if (*it == matchingValue)
    {
       myContainer.erase(it++);
    }
    else
    {
        ++it;
    }
}
10 голосов
/ 24 июня 2009

Пример с 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;
      }
   }

}
9 голосов
/ 24 июня 2009
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}
3 голосов
/ 17 июля 2010

У Виктора есть возможность сделать что-то с элементом перед удалением. (Я не смог сделать это с remove_if или remove_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<int>(), 4 ) или что-то вроде этого:

struct Predicate : public unary_function<int, bool>
{
    int mExpected;
    Predicate(int desired) : mExpected(desired) {}
    bool operator() (int input)
    {
        return ( input == mExpected );
    }
};
2 голосов
/ 12 июля 2015

1.Для std::vector<>:

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

2.Для std::map<> всегда используйте 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()
2 голосов
/ 24 июня 2009

Я предпочитаю версию с 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.

1 голос
/ 07 января 2011

Используйте тот факт, что оператор после декремента возвращает копию итератора до того, как уменьшится. Поскольку уменьшенный итератор все еще действителен после удаления текущего элемента, цикл 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;
}

Редактировать: не работает, если вы пытаетесь удалить первый элемент в списке ...

1 голос
/ 29 июня 2009

markh44 - самый ответ STL-ih. Однако обратите внимание, что в целом итераторы становятся недействительными из-за изменения контейнера, но set и map являются исключениями. Там вы можете удалять элементы и продолжать использовать итераторы, кроме случаев, когда вы удаляете тот самый элемент, на который ссылается ваш итератор.

1 голос
/ 24 июня 2009
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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...