O idioma associativo padrão de exclusão de contêiner:
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;}}
Observe que realmente queremos um forloop comum aqui, pois estamos modificando o próprio contêiner. O loop baseado em intervalo deve ser estritamente reservado para situações em que nos preocupamos apenas com os elementos. A sintaxe para o RBFL deixa isso claro, nem mesmo expondo o contêiner dentro do corpo do loop.
Editar. Antes do C ++ 11, você não podia apagar const-iteradores. Lá você teria que dizer:
for(std::map<K,V>::iterator it = m.begin(); it != m.end();){/* ... */}
Apagar um elemento de um contêiner não está em desacordo com a constância do elemento. Por analogia, sempre foi perfeitamente legítimo para delete ponde pestá um ponteiro para constante. A constância não restringe a vida; valores const em C ++ ainda podem parar de existir.
"nem mesmo expondo o contêiner dentro do corpo do loop", o que você quer dizer?
22411 Dani
2
@Dani: Bem, contraste isso com a construção do século XX for (int i = 0; i < v.size(); i++). Aqui temos que dizer v[i]dentro do loop, ou seja, devemos mencionar explicitamente o contêiner. O RBFL, por outro lado, apresenta a variável do loop que é diretamente utilizável como valor e, portanto, nenhum conhecimento do contêiner é necessário dentro do loop. Essa é uma pista do uso pretendido do RBFL para loops que não precisam ser informados sobre o contêiner. Apagar é a situação completamente oposta, onde tudo se resume ao contêiner.
22411 Kerrek SB
3
@skyhisi: De fato. Este é um dos usos legítimos do pós-incremento: primeiro incremento itpara obter o próximo iterador válido e, em seguida, apague o antigo. Não funciona ao contrário!
22411 Kerrek SB
5
Eu li em algum lugar que no C ++ 11, it = v.erase(it);agora funciona também para mapas. Ou seja, erase () em todos os elementos associativos agora retorna o próximo iterador. Portanto, o kludge antigo que exigia um pós-incremento ++ dentro do delete () não é mais necessário. Isso (se verdadeiro) é uma coisa boa, pois o kludge contava com a magia substituída pós-incremento-dentro de uma chamada de função, "corrigida" pelos mantenedores novatos para tirar o incremento da chamada de função ou trocá-lo a um pré-incremento "porque isso é apenas uma coisa estilo", etc.
Dewi Morgan
3
por que você ligaria it++nos blocos ifeelse ? não seria suficiente chamá-lo uma vez depois destes?
Nburk # 14/15
25
Pessoalmente, prefiro esse padrão que é um pouco mais claro e mais simples, às custas de uma variável extra:
for(auto it = m.cbegin(), next_it = it; it != m.cend(); it = next_it){++next_it;if(must_delete){
m.erase(it);}}
Vantagens dessa abordagem:
o incrementador de loop for faz sentido como um incrementador;
a operação de apagamento é simples, ao invés de ser misturada à lógica de incremento;
após a primeira linha do corpo do loop, o significado ite a next_itpermanência fixos durante toda a iteração, permitindo que você adicione facilmente instruções adicionais referentes a elas, sem riscar a cabeça sobre se elas funcionarão como planejadas (exceto, é claro, que você não pode usar itdepois de apagá-la) .
Eu posso pensar em outra vantagem, na verdade, se o loop chamar o código que apaga a entrada que está sendo iterada por outras anteriores (e o loop não a conhece), funcionará sem nenhum dano. A única restrição é se algo está apagando o que está sendo apontado por next_it ou sucessores. Uma lista / mapa totalmente limpo também pode ser testada.
Larswad
Essa resposta é simples e clara, mesmo que o loop seja mais complexo e possua vários níveis de lógica para decidir se deseja excluir ou não executar outras tarefas. Propus uma edição, no entanto, para torná-la um pouco mais simples. "next_it" pode ser definido como "it" no init do for para evitar erros de digitação e, como as instruções init e iteration o definem e next_it com os mesmos valores, você não precisa dizer "next_it = it;" no início do loop.
Cdgraham #
1
Lembre-se de qualquer pessoa que use esta resposta: Você deve ter "++ next_it" dentro do loop for e não na expressão da iteração. Se você tentar movê-lo para a expressão de iteração como "it = next_it ++", na última iteração, quando "it" for definido como igual a "m.cend ()", você tentará iterar "next_it" passado "m.cend ()", que é incorreto.
cdgraham
6
Resumindo, "Como removo de um mapa enquanto o itero?"
Com o mapa antigo impl: você não pode
Com o novo mapa impl: quase como o @KerrekSB sugeriu. Mas existem alguns problemas de sintaxe no que ele postou.
No mapa GCC impl (observe 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
Exemplo com estilo antigo e novo:
#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;}
impressões:
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.
Eu não entendo. Qual é o problema mi.erase(it++);?
lvella 28/01
1
@lvella see op. "Se eu usar o map.erase, ele invalidará os iteradores".
Kashyap
Seu novo método não funcionará se, após a exclusão, o mapa ficar vazio. Nesse caso, o iterador será invalidado. Então, logo após apagar, é melhor inserir if(mi.empty()) break;.
Rahat Zaman
4
O rascunho do C ++ 20 contém a função de conveniência std::erase_if.
Então você pode usar essa função para fazer isso como uma linha.
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);});
Muito triste, né? A maneira como costumo fazer isso é criar um contêiner de iteradores em vez de excluir durante a travessia. Em seguida, percorra o contêiner e 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)}
Respostas:
O idioma associativo padrão de exclusão de contêiner:
Observe que realmente queremos um
for
loop comum aqui, pois estamos modificando o próprio contêiner. O loop baseado em intervalo deve ser estritamente reservado para situações em que nos preocupamos apenas com os elementos. A sintaxe para o RBFL deixa isso claro, nem mesmo expondo o contêiner dentro do corpo do loop.Editar. Antes do C ++ 11, você não podia apagar const-iteradores. Lá você teria que dizer:
Apagar um elemento de um contêiner não está em desacordo com a constância do elemento. Por analogia, sempre foi perfeitamente legítimo para
delete p
ondep
está um ponteiro para constante. A constância não restringe a vida; valores const em C ++ ainda podem parar de existir.fonte
for (int i = 0; i < v.size(); i++)
. Aqui temos que dizerv[i]
dentro do loop, ou seja, devemos mencionar explicitamente o contêiner. O RBFL, por outro lado, apresenta a variável do loop que é diretamente utilizável como valor e, portanto, nenhum conhecimento do contêiner é necessário dentro do loop. Essa é uma pista do uso pretendido do RBFL para loops que não precisam ser informados sobre o contêiner. Apagar é a situação completamente oposta, onde tudo se resume ao contêiner.it
para obter o próximo iterador válido e, em seguida, apague o antigo. Não funciona ao contrário!it = v.erase(it);
agora funciona também para mapas. Ou seja, erase () em todos os elementos associativos agora retorna o próximo iterador. Portanto, o kludge antigo que exigia um pós-incremento ++ dentro do delete () não é mais necessário. Isso (se verdadeiro) é uma coisa boa, pois o kludge contava com a magia substituída pós-incremento-dentro de uma chamada de função, "corrigida" pelos mantenedores novatos para tirar o incremento da chamada de função ou trocá-lo a um pré-incremento "porque isso é apenas uma coisa estilo", etc.it++
nos blocosif
eelse
? não seria suficiente chamá-lo uma vez depois destes?Pessoalmente, prefiro esse padrão que é um pouco mais claro e mais simples, às custas de uma variável extra:
Vantagens dessa abordagem:
it
e anext_it
permanência fixos durante toda a iteração, permitindo que você adicione facilmente instruções adicionais referentes a elas, sem riscar a cabeça sobre se elas funcionarão como planejadas (exceto, é claro, que você não pode usarit
depois de apagá-la) .fonte
Resumindo, "Como removo de um mapa enquanto o itero?"
No mapa GCC impl (observe GXX_EXPERIMENTAL_CXX0X ):
Exemplo com estilo antigo e novo:
impressões:
fonte
mi.erase(it++);
?if(mi.empty()) break;
.O rascunho do C ++ 20 contém a função de conveniência
std::erase_if
.Então você pode usar essa função para fazer isso como uma linha.
fonte
Muito triste, né? A maneira como costumo fazer isso é criar um contêiner de iteradores em vez de excluir durante a travessia. Em seguida, percorra o contêiner e use map.erase ()
fonte
Supondo C ++ 11, aqui está um corpo de loop de uma linha, se isso for consistente com o seu estilo de programação:
Algumas outras pequenas mudanças de estilo:
Map::const_iterator
) quando possível / conveniente, usando demaisauto
.using
para tipos de modelo, paraMap::const_iterator
facilitar a leitura / manutenção de tipos auxiliares ( ).fonte