Come rimuovo da una mappa durante l'iterazione? piace:
std::map<K, V> map;
for(auto i : map)
if(needs_removing(i))
// remove it from the map
Se lo uso map.erase
invaliderò gli iteratori
Come rimuovo da una mappa durante l'iterazione? piace:
std::map<K, V> map;
for(auto i : map)
if(needs_removing(i))
// remove it from the map
Se lo uso map.erase
invaliderò gli iteratori
Risposte:
Il contenitore associativo standard cancella il linguaggio:
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;
}
}
Si noti che vogliamo davvero un normale for
ciclo qui, poiché stiamo modificando il contenitore stesso. Il loop basato sul range dovrebbe essere strettamente riservato alle situazioni in cui ci preoccupiamo solo degli elementi. La sintassi per l'RBFL lo chiarisce senza nemmeno esporre il contenitore all'interno del corpo del loop.
Modificare. Prima di C ++ 11, non era possibile cancellare costanti. Lì dovresti dire:
for (std::map<K,V>::iterator it = m.begin(); it != m.end(); ) { /* ... */ }
La cancellazione di un elemento da un contenitore non è in contrasto con la costanza dell'elemento. Per analogia, è sempre stato perfettamente legittimo delete p
dove si p
trova un puntatore a costante. La costanza non limita la vita; i valori const in C ++ possono ancora smettere di esistere.
for (int i = 0; i < v.size(); i++)
. Qui dobbiamo dire v[i]
all'interno del ciclo, cioè dobbiamo menzionare esplicitamente il contenitore. D'altra parte, l'RBFL introduce la variabile loop che è direttamente utilizzabile come valore e quindi non è richiesta alcuna conoscenza del contenitore all'interno del loop. Questo è un indizio sull'uso previsto dell'RBFL per i loop che non devono conoscere il contenitore. La cancellazione è la situazione completamente opposta, in cui si tratta del contenitore.
it
per ottenere il successivo iteratore valido e quindi cancellare quello vecchio. Non funziona al contrario!
it = v.erase(it);
ora funziona anche per le mappe, ovvero, cancella () su tutti gli elementi associativi ora restituisce il successivo iteratore. Quindi il vecchio kludge che richiedeva un post-incremento ++ all'interno di delete (), non è più necessario. Questa (se vera) è una buona cosa, poiché il kludge si basava sulla magia overridden-post-increment-all'interno-di-una-funzione-chiamata, "riparata" dai manutentori principianti per prendere l'incremento dalla chiamata di funzione o per scambiarlo a un preincremento "perché è solo una cosa di stile", ecc.
it++
i blocchi if
e else
? non sarebbe sufficiente chiamarlo una volta dopo questi?
Personalmente preferisco questo modello che è leggermente più chiaro e più semplice, a scapito di una variabile aggiuntiva:
for (auto it = m.cbegin(), next_it = it; it != m.cend(); it = next_it)
{
++next_it;
if (must_delete)
{
m.erase(it);
}
}
Vantaggi di questo approccio:
it
e next_it
rimanere fisso durante l'iterazione, consentendoti di aggiungere facilmente ulteriori istruzioni che si riferiscono a loro senza fare headcratch sul fatto che funzioneranno come previsto (tranne ovviamente che non puoi usare it
dopo averlo cancellato) .In breve "Come rimuovo da una mappa durante l'iterazione?"
Da GCC map impl (nota 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
Esempio con vecchio e nuovo stile:
#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;
}
stampe:
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;
.
La bozza C ++ 20 contiene la funzione convenienza std::erase_if
.
Quindi puoi usare quella funzione per farlo come una linea.
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);});
Abbastanza triste, eh? Il modo in cui lo faccio di solito è costruire un contenitore di iteratori invece di eliminarlo durante l'attraversamento. Quindi scorrere il contenitore e utilizzare 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)
}
Supponendo C ++ 11, ecco un corpo di loop a una riga, se questo è coerente con il tuo stile di programmazione:
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);
Un paio di altri piccoli cambiamenti di stile:
Map::const_iterator
) quando possibile / conveniente, oltre l'utilizzo auto
.using
per i tipi di modello, per rendere Map::const_iterator
più facili da leggere / mantenere i tipi ausiliari ( ).