Comment supprimer une carte lors de son itération? comme:
std::map<K, V> map;
for(auto i : map)
if(needs_removing(i))
// remove it from the map
Si je l'utilise, map.erase
cela invalidera les itérateurs
Comment supprimer une carte lors de son itération? comme:
std::map<K, V> map;
for(auto i : map)
if(needs_removing(i))
// remove it from the map
Si je l'utilise, map.erase
cela invalidera les itérateurs
Réponses:
L'idiome d'effacement de conteneur associatif standard:
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;
}
}
Notez que nous voulons vraiment une for
boucle ordinaire ici, car nous modifions le conteneur lui-même. La boucle basée sur la plage doit être strictement réservée aux situations où nous ne nous soucions que des éléments. La syntaxe du RBFL rend cela clair en n'exposant même pas le conteneur à l'intérieur du corps de la boucle.
Éditer. Avant C ++ 11, vous ne pouviez pas effacer les itérateurs const. Là, il faudrait dire:
for (std::map<K,V>::iterator it = m.begin(); it != m.end(); ) { /* ... */ }
L'effacement d'un élément d'un conteneur n'est pas en contradiction avec la constance de l'élément. Par analogie, il a toujours été parfaitement légitime de savoir delete p
où se p
trouve un pointeur vers une constante. La constness ne limite pas la durée de vie; Les valeurs const en C ++ peuvent toujours cesser d'exister.
for (int i = 0; i < v.size(); i++)
. Ici, nous devons dire v[i]
à l'intérieur de la boucle, c'est-à-dire qu'il faut mentionner explicitement le conteneur. Le RBFL, d'autre part, introduit la variable de boucle qui est directement utilisable comme valeur, et donc aucune connaissance du conteneur n'est requise à l'intérieur de la boucle. Ceci est un indice de l'utilisation prévue du RBFL pour les boucles qui n'ont pas besoin de connaître le conteneur. L'effacement est la situation complètement opposée, où tout tourne autour du conteneur.
it
pour obtenir l'itérateur suivant et valide, puis effacer l'ancien. Cela ne fonctionne pas dans l'autre sens!
it = v.erase(it);
fonctionne maintenant aussi pour les cartes, c'est-à-dire que erase () sur tous les éléments associatifs renvoie maintenant l'itérateur suivant. Ainsi, l'ancien kludge qui nécessitait un post-incrément ++ dans le delete (), n'est plus nécessaire. Ceci (si c'est vrai) est une bonne chose, car le kludge reposait sur la magie surchargée de post-incrémentation dans un appel de fonction, "corrigée" par les responsables débutants pour supprimer l'incrément de l'appel de fonction, ou pour l'échanger à un pré-incrément "parce que c'est juste une chose de style", etc.
it++
les blocs if
et else
? ne serait-il pas suffisant de l'appeler une fois après ceux-ci?
Personnellement, je préfère ce schéma qui est légèrement plus clair et plus simple, au détriment d'une variable supplémentaire:
for (auto it = m.cbegin(), next_it = it; it != m.cend(); it = next_it)
{
++next_it;
if (must_delete)
{
m.erase(it);
}
}
Avantages de cette approche:
it
et next_it
reste fixe tout au long de l'itération, ce qui vous permet d'ajouter facilement des instructions supplémentaires y faisant référence sans vous demander si elles fonctionneront comme prévu (sauf bien sûr que vous ne pouvez pas l'utiliser it
après l'avoir effacée) .En bref "Comment supprimer d'une carte lors de son itération?"
Depuis GCC map impl (remarque 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
Exemple avec l'ancien et le nouveau style:
#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;
}
imprime:
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;
.
Le projet C ++ 20 contient la fonction de commodité std::erase_if
.
Vous pouvez donc utiliser cette fonction pour le faire comme une seule ligne.
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);});
Assez triste, hein? La façon dont je le fais habituellement est de créer un conteneur d'itérateurs au lieu de les supprimer pendant la traversée. Ensuite, parcourez le conteneur et utilisez 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)
}
En supposant C ++ 11, voici un corps de boucle à une seule ligne, si cela est cohérent avec votre style de programmation:
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);
Quelques autres changements de style mineurs:
Map::const_iterator
) lorsque cela est possible / pratique, en utilisant auto
.using
pour les types de modèles, pour rendre les types auxiliaires ( Map::const_iterator
) plus faciles à lire / maintenir.