Jak usunąć z mapy podczas iteracji? lubić:
std::map<K, V> map;
for(auto i : map)
if(needs_removing(i))
// remove it from the map
Jeśli map.erase
go użyję, unieważni iteratory
Jak usunąć z mapy podczas iteracji? lubić:
std::map<K, V> map;
for(auto i : map)
if(needs_removing(i))
// remove it from the map
Jeśli map.erase
go użyję, unieważni iteratory
Odpowiedzi:
Standardowy idiom usuwania kontenerów asocjacyjnych:
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;
}
}
Zwróć uwagę, że naprawdę chcemy for
tutaj zwykłej pętli, ponieważ modyfikujemy sam kontener. Pętla zakresowa powinna być zarezerwowana dla sytuacji, w których zależy nam tylko na elementach. Składnia RBFL wyjaśnia to jasno, nawet nie ujawniając kontenera wewnątrz ciała pętli.
Edytować. Przed C ++ 11 nie można było usunąć stałych iteratorów. Tam musiałbyś powiedzieć:
for (std::map<K,V>::iterator it = m.begin(); it != m.end(); ) { /* ... */ }
Usunięcie elementu z kontenera nie jest sprzeczne ze stałością elementu. Analogicznie, zawsze było całkowicie uzasadnione, delete p
gdzie p
znajduje się wskaźnik do stałej. Stałość nie ogranicza życia; wartości const w C ++ mogą nadal przestać istnieć.
for (int i = 0; i < v.size(); i++)
. Tutaj musimy powiedzieć v[i]
wewnątrz pętli, czyli musimy wyraźnie wspomnieć o kontenerze. Z drugiej strony RBFL wprowadza zmienną pętli, która jest bezpośrednio używana jako wartość, więc nie jest wymagana znajomość kontenera wewnątrz pętli. Jest to wskazówka co do zamierzonego użycia RBFL dla pętli, które nie muszą wiedzieć o kontenerze. Kasowanie to zupełnie odwrotna sytuacja, w której chodzi o pojemnik.
it
celu uzyskania następnego, prawidłowego iteratora, a następnie usunięcie starego. To nie działa w drugą stronę!
it = v.erase(it);
teraz działa też dla map, czyli erase () na wszystkich elementach asocjacyjnych zwraca teraz następny iterator. Więc stary kludge, który wymagał post-inkrementacji ++ w ramach delete (), nie jest już potrzebny. To (jeśli prawda) jest Dobra Rzecz, ponieważ kludge polegał na zastępowanej magii wywołania funkcji po inkrementacji, „naprawionej” przez początkujących opiekunów, aby usunąć przyrost z wywołania funkcji lub zamienić go do preinkrement „bo to tylko kwestia stylu” itp.
it++
do bloków if
i else
? czy nie wystarczy nazwać to raz po tych?
Osobiście wolę ten wzór, który jest nieco jaśniejszy i prostszy kosztem dodatkowej zmiennej:
for (auto it = m.cbegin(), next_it = it; it != m.cend(); it = next_it)
{
++next_it;
if (must_delete)
{
m.erase(it);
}
}
Zalety tego podejścia:
it
i next_it
pozostaje niezmienne przez całą iterację, co pozwala na łatwe dodawanie dodatkowych instrukcji odnoszących się do nich bez zastanawiania się, czy będą działać zgodnie z przeznaczeniem (z wyjątkiem oczywiście, że nie można ich użyć it
po skasowaniu) .W skrócie „Jak usunąć z mapy podczas iteracji?”
Z impl mapy GCC (uwaga 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
Przykład ze starym i nowym stylem:
#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;
}
wydruki:
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;
.
Wersja robocza C ++ 20 zawiera wygodną funkcję std::erase_if
.
Możesz więc użyć tej funkcji, aby zrobić to jako jedną linijkę.
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);});
Całkiem smutne, co? Sposób, w jaki zwykle to robię, to budowanie kontenera iteratorów zamiast usuwania podczas przechodzenia. Następnie przejdź przez kontener i użyj 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)
}
Zakładając C ++ 11, oto treść pętli jednowierszowej, jeśli jest to zgodne z Twoim stylem programowania:
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);
Kilka innych drobnych zmian w stylu:
Map::const_iterator
), jeśli to możliwe / wygodne, przed użyciem auto
.using
dla typów szablonów, aby Map::const_iterator
ułatwić odczytywanie / utrzymywanie typów pomocniczych ( ).