Вопрос по stl, c++ – Стереть / удалить содержимое с карты (или любого другого контейнера STL) во время итерации

26

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

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

Ваш Ответ

9   ответов
1

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

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

2

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 петля.

34

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

MyContainer::iterator it = myContainer.begin();
while(it != myContainer.end())
{
    if (*it == matchingValue)
    {
       myContainer.erase(it++);
    }
    else
    {
        ++it;
    }
}
Постинкремент выполняется перед вызовом erase (), потому что для вызова требуется значение. Erase () получает копию необозначенного указателя.
+1. & Quot; myContainer.erase (это ++); & Quot; тонкий - он правильно выполняет приращениеbefore вызывая erase (), когда это все еще допустимо, передавая (копию)unincremented итератор этой функции.
ВАЖНО: Этот код работает для map, set и list, но он НЕ будет работать для вектора - стирание из вектора делает недействительными итераторы для этого и всех последующих элементов (23.2.4.3/3). Уронили +1 на данный момент, повторно + 1, когда вы упомянули об этом.
@Ismael: Postincrement возвращает неизмененныйcopy его операнда до приращения. Итераторы STL гарантируют это.
@Ismael: вызовы функций являются точками последовательности, поэтому побочные эффекты от приращения гарантированно будут выполнены до начала вызова стирания.
1

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

2

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.
    }
}
For std::list use std::list::erase()
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; .. и не увеличиватьit вfor условно!
исходный код предшествовал C ++ 11, а в прежние времена тип возврата erase был недействительным. Из того, что я помню, это то, что итератор не может быть разыменован, но все еще может быть продвинутым.
Версия карты пытается увеличить итератор после того, как он был удален (и поэтому недействителен).
Хотя не работает с картой.
9
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?
bind1st создает функцию, подобную объекту, которая, по сути, дает вам вызов функции с постоянным первым параметром - так что в этом примере он будет иметь эффект равный равному (4, X), где X происходит из последовательности, которую мы перебираем. В результате каждое значение в последовательности сравнивается с 4.
3

алением. (Я не смог сделать это с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 );
    }
};
9

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

}
Хороший пример, но для работы с картой (и ее друзьями) нельзя использовать возвращаемое значение erase () - по какой-то причине std :: map :: erase () возвращает void (MS будет связываться с вашей головой на этом)
Не знал об этом, но разве итератор не вернулся после удаления нового, проверенного? Звучит очень странно, что он вернет неверный итератор?
@j_random_hacker: вы правы, что он делает недействительными любые итераторы..но std :: vector :: erase возвращаетnew, valid итератор элемента после стертого (или конечного). Этот код совершенно действителен.
Какой смысл возвращать итератор?
@j_random_hacker: но remove () \ remove_if () также работает только с последовательностями?

Похожие вопросы