Как удалить с карты во время итерации? лайк:
std::map<K, V> map;
for(auto i : map)
if(needs_removing(i))
// remove it from the map
Если я использую map.erase
его, аннулирует итераторы
Как удалить с карты во время итерации? лайк:
std::map<K, V> map;
for(auto i : map)
if(needs_removing(i))
// remove it from the map
Если я использую map.erase
его, аннулирует итераторы
Ответы:
Стандартная ассоциативно-контейнерная идиома стирания:
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 проясняет это, даже не раскрывая контейнер внутри тела цикла.
Редактировать. До C ++ 11 вы не могли стереть const-итераторы. Там вы должны сказать:
for (std::map<K,V>::iterator it = m.begin(); it != m.end(); ) { /* ... */ }
Стирание элемента из контейнера не противоречит его постоянству. По аналогии, это всегда было совершенно законноdelete p
где p
указатель на константу. Константность не ограничивает продолжительность жизни; константные значения в C ++ могут прекратить существование.
for (int i = 0; i < v.size(); i++)
. Здесь мы должны сказать v[i]
внутри цикла, то есть мы должны явно упомянуть контейнер. RBFL, с другой стороны, представляет переменную цикла, которая непосредственно используется в качестве значения, и поэтому знание цикла внутри цикла не требуется. Это ключ к предполагаемому использованию RBFL для циклов, которые не должны знать о контейнере. Стирание - это совершенно противоположная ситуация, когда речь идет о контейнере.
it
чтобы получить следующий, действительный итератор, а затем стереть старый. Это не работает наоборот!
it = v.erase(it);
теперь работает и для карт. То есть, erase () для всех ассоциативных элементов теперь возвращает следующий итератор. Таким образом, старый kludge, который требовал постинкремент ++ в delete (), больше не нужен. Это (если true) хорошая вещь, поскольку kludge полагался на магию переопределенного пост-инкремента в вызове функции, «исправленную» сопровождающими новичка, чтобы исключить приращение из вызова функции или обменять его до предчувствия "потому что это просто вещь стиля" и т. д.
it++
в if
и else
блоки? Разве не достаточно будет позвонить один раз после этого?
Я лично предпочитаю эту схему, которая немного яснее и проще за счет дополнительной переменной:
for (auto it = m.cbegin(), next_it = it; it != m.cend(); it = next_it)
{
++next_it;
if (must_delete)
{
m.erase(it);
}
}
Преимущества такого подхода:
it
и next_it
остается неизменным на протяжении всей итерации, что позволяет вам легко добавлять дополнительные операторы, ссылающиеся на них, без разбора на вопрос, будут ли они работать как задумано (за исключением того, что вы не можете использовать его it
после стирания) ,Короче говоря "Как удалить с карты при ее повторении?"
Из карты 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'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
Пример со старым и новым стилем:
#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.
mi.erase(it++);
?
if(mi.empty()) break;
.
Черновик 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);});
Довольно грустно, а? Обычно я делаю это для создания контейнера итераторов вместо удаления во время обхода. Затем переберите контейнер и используйте 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)
}
Предполагая 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);
Пара других незначительных изменений стиля:
Map::const_iterator
), когда это возможно / удобно, через использование auto
.using
для типов шаблонов, чтобы Map::const_iterator
облегчить чтение / обслуживание вспомогательных типов ( ).