El idioma estándar de borrado de contenedor asociativo:
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 forciclo ordinario aquí, ya que estamos modificando el contenedor en sí. El bucle basado en el rango debe estar estrictamente reservado para situaciones en las que solo nos interesan 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 los iteradores constantes. Ahí tendrías que decir:
for(std::map<K,V>::iterator it = m.begin(); it != m.end();){/* ... */}
Borrar un elemento de un contenedor no está reñido con la consistencia del elemento. Por analogía, siempre ha sido perfectamente legítimo delete pdónde pestá un puntero a constante. La constidad no limita la vida útil; Los valores const en C ++ aún pueden dejar de existir.
"ni siquiera exponiendo el contenedor dentro del cuerpo del bucle" ¿a qué te refieres?
Dani
2
@Dani: Bueno, contrasta esto con la construcción del siglo XX for (int i = 0; i < v.size(); i++). Aquí tenemos que decir v[i]dentro del ciclo, es decir, debemos mencionar explícitamente el contenedor. El RBFL, por otro lado, introduce la variable de bucle que se puede usar directamente como valor, por lo que no se requiere conocimiento del contenedor dentro del bucle. Esta es una pista sobre el uso previsto del RBFL para los bucles que no tienen que saber sobre el contenedor. Borrar es la situación completamente opuesta, donde se trata del contenedor.
Kerrek SB
3
@skyhisi: De hecho. Este es uno de los usos legítimos del post-incremento: primer incremento itpara obtener el siguiente iterador válido y luego borrar el anterior. ¡No funciona al revés!
Kerrek SB
55
Leí en alguna parte que en C ++ 11, it = v.erase(it);ahora también funciona para mapas. Es decir, borrar () en todos los elementos asociativos ahora devuelve el siguiente iterador. Entonces, el viejo kludge que requería un post-incremento ++ dentro de delete (), ya no es necesario. Esto (si es cierto) es una buena cosa, ya que el kludge se basó en la magia anulada-post-incremento-dentro-de-una-función, "arreglada" por mantenedores novatos para quitar el incremento de la llamada a la función, o intercambiarlo a un preincremento "porque eso es solo una cosa de estilo", etc.
Dewi Morgan
3
¿por qué llamar it++a los ifyelse bloques? ¿No sería suficiente llamarlo una vez después de estos?
nburk
25
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 = it; it != m.cend(); it = next_it){++next_it;if(must_delete){
m.erase(it);}}
Ventajas de este enfoque:
el incremento de bucle for tiene sentido como un incremento;
la operación de borrado es un borrado simple, en lugar de ser mezclado con lógica de incremento;
después de la primera línea del cuerpo del bucle, el significado de ity next_itpermanecer fijo a lo largo de la iteración, lo que le permite agregar fácilmente declaraciones adicionales que se refieren a ellos sin tachar la cabeza sobre si funcionarán según lo previsto (excepto, por supuesto, que no puede usar itdespués de borrarlo) .
En realidad, puedo pensar en otra ventaja, si el bucle invoca un código que borra esa entrada que se repite o las anteriores (y el bucle no lo sabe) funcionará sin ningún daño. La única restricción es si algo está borrando lo que apunta next_it o los sucesores. También se puede probar una lista / mapa totalmente borrado.
Larswad el
Esta respuesta es simple y clara, incluso si el ciclo es más complejo y tiene múltiples niveles de lógica para decidir si eliminar o no, o hacer otras tareas. Sin embargo, he propuesto una edición para que sea un poco más simple. "next_it" se puede establecer en "it" en for's init para evitar errores tipográficos, y dado que las instrucciones init e iteration lo establecen y next_it a los mismos valores, no necesita decir "next_it = it;" al comienzo del ciclo.
cdgraham
1
Tenga en cuenta a cualquiera que use esta respuesta: debe tener "++ next_it" dentro del bucle for, y no en la expresión de iteración. Si intenta moverlo a la expresión de iteración como "it = next_it ++", en la última iteración, cuando "it" se establecerá igual a "m.cend ()", intentará iterar "next_it" pasado "m.cend ()", que es erróneo.
cdgraham
6
En resumen "¿Cómo elimino un mapa mientras lo itero?"
Con el viejo mapa impl: No puedes
Con nuevo mapa impl: casi como sugirió @KerrekSB. Pero hay algunos problemas de sintaxis en lo que publicó.
Desde el mapa GCC 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>usingnamespace std;typedefmap<int,int> t_myMap;typedefvector<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;});return0;}
huellas dactilares:
main() ENTRY
Init1-12-13-14-15-16-1
after old style erase of 3&4..1-12-15-16-1
after new style erase of 51-12-16-1Process returned 0(0x0) execution time :0.021 s
Press any key to continue.
No lo entiendo ¿Cuál es el problema con mi.erase(it++);?
lvella
1
@lvella ver op. "Si uso map.erase, invalidará los iteradores".
Kashyap
Su nuevo método no funcionará si después de borrar, el mapa queda vacío. En ese caso, el iterador será invalidado. Entonces, poco después de borrar, es mejor insertarlo if(mi.empty()) break;.
Rahat Zaman
4
El borrador de C ++ 20 contiene la función de conveniencia std::erase_if.
Entonces puede usar esa función para hacerlo como una línea.
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);});
Bastante triste, ¿eh? La forma en que generalmente lo hago es construir un contenedor de iteradores en lugar de eliminar durante el recorrido. Luego 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)}
Respuestas:
El idioma estándar de borrado de contenedor asociativo:
Tenga en cuenta que realmente queremos un
for
ciclo ordinario aquí, ya que estamos modificando el contenedor en sí. El bucle basado en el rango debe estar estrictamente reservado para situaciones en las que solo nos interesan 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 los iteradores constantes. Ahí tendrías que decir:
Borrar un elemento de un contenedor no está reñido con la consistencia del elemento. Por analogía, siempre ha sido perfectamente legítimo
delete p
dóndep
está un puntero a constante. La constidad no limita la vida útil; Los valores const en C ++ aún pueden dejar de existir.fuente
for (int i = 0; i < v.size(); i++)
. Aquí tenemos que decirv[i]
dentro del ciclo, es decir, debemos mencionar explícitamente el contenedor. El RBFL, por otro lado, introduce la variable de bucle que se puede usar directamente como valor, por lo que no se requiere conocimiento del contenedor dentro del bucle. Esta es una pista sobre el uso previsto del RBFL para los bucles que no tienen que saber sobre el contenedor. Borrar es la situación completamente opuesta, donde se trata del contenedor.it
para obtener el siguiente iterador válido y luego borrar el anterior. ¡No funciona al revés!it = v.erase(it);
ahora también funciona para mapas. Es decir, borrar () en todos los elementos asociativos ahora devuelve el siguiente iterador. Entonces, el viejo kludge que requería un post-incremento ++ dentro de delete (), ya no es necesario. Esto (si es cierto) es una buena cosa, ya que el kludge se basó en la magia anulada-post-incremento-dentro-de-una-función, "arreglada" por mantenedores novatos para quitar el incremento de la llamada a la función, o intercambiarlo a un preincremento "porque eso es solo una cosa de estilo", etc.it++
a losif
yelse
bloques? ¿No sería suficiente llamarlo una vez después de estos?Personalmente, prefiero este patrón, que es un poco más claro y simple, a expensas de una variable adicional:
Ventajas de este enfoque:
it
ynext_it
permanecer fijo a lo largo de la iteración, lo que le permite agregar fácilmente declaraciones adicionales que se refieren a ellos sin tachar la cabeza sobre si funcionarán según lo previsto (excepto, por supuesto, que no puede usarit
después de borrarlo) .fuente
En resumen "¿Cómo elimino un mapa mientras lo itero?"
Desde el mapa GCC impl (nota GXX_EXPERIMENTAL_CXX0X ):
Ejemplo con estilo antiguo y nuevo:
huellas dactilares:
fuente
mi.erase(it++);
?if(mi.empty()) break;
.El borrador de C ++ 20 contiene la función de conveniencia
std::erase_if
.Entonces puede usar esa función para hacerlo como una línea.
fuente
Bastante triste, ¿eh? La forma en que generalmente lo hago es construir un contenedor de iteradores en lugar de eliminar durante el recorrido. Luego recorra el contenedor y use map.erase ()
fuente
Suponiendo C ++ 11, aquí hay un cuerpo de bucle de una línea, si esto es consistente con su estilo de programación:
Un par de otros cambios menores de estilo:
Map::const_iterator
) cuando sea posible / conveniente, sobre el usoauto
.using
para tipos de plantilla, para hacer que los tipos auxiliares (Map::const_iterator
) sean más fáciles de leer / mantener.fuente