Как удалить с карты при ее повторении?

Как удалить с карты во время ее итерации? как:

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

Если я использую map.erase, это приведет к недействительности итераторов

Ответ 1

Стандартный идентификатор стирания ассоциативного контейнера:

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

Обратите внимание, что нам действительно нужен обычный цикл for, так как мы модифицируем сам контейнер. Цикл, основанный на диапазоне, должен быть строго зарезервирован для ситуаций, когда мы заботимся только об элементах. Синтаксис RBFL делает это ясным, даже не подвергая контейнер внутри тела цикла.

Изменить. Pre-С++ 11, вы не сможете стереть константы-константы. Там вы должны сказать:

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

Удаление элемента из контейнера не противоречит константе элемента. По аналогии, он всегда был совершенно законным delete p, где p является указателем на константу. Константа не ограничивает время жизни; константы в С++ все еще могут перестать существовать.

Ответ 2

Довольно грустно, а? Обычно я делаю это, создавая контейнер итераторов вместо удаления во время обхода. Затем пройдите через контейнер и используйте 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)
}

Ответ 3

Короче: "Как удалить с карты во время ее итерации?"

  • С помощью старой карты impl: вы не можете
  • С новой подсказкой карты: почти так же, как предложил @KerrekSB. Но есть некоторые проблемы с синтаксисом в том, что он опубликовал.

Из карты 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 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 responsibility.
       */
      void
      erase(iterator __position)
      { _M_t.erase(__position); }
#endif

Пример со старым и новым стилем:

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

печатает:

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.

Ответ 4

Я лично предпочитаю этот шаблон, который немного яснее и проще, за счет дополнительной переменной:

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

Преимущества такого подхода:

  • инкрементер цикла for имеет смысл в качестве инкремента;
  • операция стирания - это простое удаление, а не смешивание с логикой нарастания;
  • после первой строки тела цикла значение it и next_it остается фиксированным на протяжении всей итерации, что позволяет вам легко добавлять дополнительные утверждения, ссылаясь на них, без учета того, будут ли они работать по назначению (за исключением конечно, что вы не можете использовать it после его стирания).

Ответ 5

Я думаю, что наиболее разумный способ (по крайней мере, в С++ 14) стирать элемент карты во время итерации:

for(auto it = m.begin(); it != m.end(); ++it) {
    if(delete_condition) {
        m.erase(it);
    }
}