c++ - ejemplo - ¿Cómo eliminar de un mapa mientras lo itera?
pair c++ (5)
Creo que la manera más sensata (al menos en C ++ 14) para borrar un elemento del mapa durante la iteración es:
for(auto it = m.begin(); it != m.end(); ++it) {
if(delete_condition) {
m.erase(it);
}
}
¿Cómo elimino de un mapa mientras lo itero? me gusta:
std::map<K, V> map;
for(auto i : map)
if(needs_removing(i))
// remove it from the map
Si uso map.erase
invalidará los iteradores
El idioma de borrado de contenedor asociativo estándar:
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;
}
}
Tenga en cuenta que realmente queremos un bucle for
normal aquí, ya que estamos modificando el contenedor en sí. El ciclo basado en rango debe reservarse estrictamente para situaciones en las que solo nos importan los elementos. La sintaxis para el RBFL lo aclara al no exponer el contenedor dentro del cuerpo del bucle.
Editar. Antes de C ++ 11, no se podían borrar const iterators. Allí tendrías que decir:
for (std::map<K,V>::iterator it = m.begin(); it != m.end(); ) { /* ... */ }
Borrar un elemento de un contenedor no está en desacuerdo con la consistencia del elemento. Por analogía, siempre ha sido perfectamente legítimo delete p
donde p
es un puntero a constante. Constness no restringe la vida; los valores de const en C ++ aún pueden dejar de existir.
En resumen, "¿Cómo elimino de un mapa al iterarlo?"
- Con el antiguo mapa impl: no se puede
- Con nuevo mapa impl: casi como @KerrekSB sugirió. Pero hay algunos problemas de sintaxis en lo que publicó.
Desde GCC mapa 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
Ejemplo con estilo antiguo y nuevo:
#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;
}
huellas dactilares:
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.
Muy triste, ¿eh? La forma en que normalmente lo hago es crear un contenedor de iteradores en lugar de eliminar durante el recorrido. A continuación, recorra el contenedor y use 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)
}
Yo personalmente prefiero este patrón que es un poco más claro y simple, a expensas de una variable adicional:
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);
}
}
Ventajas de este enfoque:
- el incrementor for loop tiene sentido como incrementor;
- la operación de borrado es un simple borrado, en lugar de mezclarse con la lógica de incremento;
- después de la primera línea del cuerpo del bucle, su significado y
next_it
permanecen fijos a lo largo de la iteración, lo que le permite agregar fácilmente declaraciones adicionales que se refieren a ellos sin que se note si funcionarán según lo previsto (excepto, por supuesto, que no puede usarlo después borrándolo).