2014-06-10 2 views
1

Je veux itérer sur tous les éléments dans un std::multimap (toutes les valeurs de toutes les clés), et supprimer toutes les entrées qui satisfont une condition:itérer sur std :: multimap et supprimer certaines entrées

#include <map> 

typedef int KEY_TYPE; 
typedef int VAL_TYPE; 

bool shouldRemove(const KEY_TYPE&, const VAL_TYPE&); 

void removeFromMap(std::multimap<KEY_TYPE,VAL_TYPE>& map){ 
    for (auto it = map.begin(); it != map.end(); it++){ 
     if (shouldRemove(it->first,it->second)) 
      map.erase(it); 
    } 
} 

Les travaux d'itération à moins que le premier élément est supprimé, et l'erreur suivante est lancée alors:

carte/set iterator pas Incrementable

Comment le removeFromMap fonction être réécrit afin de fonctionner correctement? Le code devrait fonctionner pour tous les types de clés et de valeurs de la carte.

J'utilise C++ 11 et Visual Studio 2013.

+0

Normalement, on utilise la valeur de retour de map.erase (itérateur), puisqu'il renvoie l'itérateur à l'élément ou à la fin suivant, si c'était le dernier élément – AquilaRapax

+0

@Erbureth Vous devriez ajouter cela comme réponse :-) –

+0

@ KarlNicoll Cependant, après avoir creusé plus profondément, l'idiome Erase-remove n'est pas applicable sur 'std :: set',' std :: map' et les amis, car leur type de valeur n'est pas 'MoveAssignable'. Et je n'ai aucune idée de la façon de l'implémenter sur de tels conteneurs, car cela fonctionne en déplaçant des éléments. – Erbureth

Répondre

5

Vous devez augmenter votre iterator avant que vous faites l'effacement. Lorsque vous faites map.erase(it); l'itérateur it devient invalide. Cependant, les autres itérateurs de la carte seront toujours valides. Par conséquent, vous pouvez résoudre ce problème en effectuant un post-augmentation de la iterator ...

auto it = map.begin(); 
const auto end = map.end(); 

while (it != end) 
{ 
    if (shouldRemove(it->first,it->second)) 
    { 
     map.erase(it++); 
       // ^^ Note the increment here. 
    } 
    else 
    { 
     ++it; 
    } 
} 

Le post-incrément appliqué à it à l'intérieur des paramètres map.erase() veillera à ce que it reste valable après l'article est effacé par incrémenter l'itérateur pour pointer vers l'élément suivant sur la carte juste avant d'effacer.

map.erase(it++); 

... est fonctionnellement équivalent à ...

auto toEraseIterator = it; // Remember the iterator to the item we want to erase. 
++it;       // Move to the next item in the map. 
map.erase(toEraseIterator); // Erase the item. 

Comme @imbtfab souligne dans les commentaires, vous pouvez également utiliser it = map.erase(it) pour faire la même chose en C++ 11 sans besoin de post-incrémentation.

Notez également que la boucle for a maintenant été remplacée par une boucle while car nous contrôlons l'itérateur manuellement. De plus, si vous cherchez à rendre votre fonction removeFromMap aussi générique que possible, vous devriez envisager d'utiliser des paramètres de modèle et de transmettre vos itérateurs directement, plutôt que de passer une référence à la multi-carte. Cela vous permettra d'utiliser n'importe quel type de conteneur de type carte, plutôt que de forcer l'insertion d'un multimap.

par exemple.

template <typename Iterator> 
void removeFromMap(Iterator it, const Iterator &end){ 
    ... 
} 

Voici comment les fonctions standard C++ <algorithm> faire aussi (par exemple std::sort(...)).

+0

qui fonctionne pour moi, merci! Cependant, je ne comprends pas pourquoi l'utilisation de la méthode de post-incrémentation résout le problème - pourquoi l'effacer? it ++; 'et' effacer (it ++); 'diffèrent? – muffel

+1

Quand vous faites 'erase (it) ', la variable' it' n'est plus utilisable.Essayer d'incrémenter 'it' après qu'il a été effacé est un comportement non défini. En faisant 'erase (it ++)', vous incrémentez 'it' * avant que * erase ne soit appelé, mais passez quand même l'ancien itérateur non incrémenté dans la fonction' erase'. C'est équivalent à ce qui suit: 'auto newIt = std :: next (it); map.erase (it); c'est = newIt; Vous pourriez avoir une meilleure compréhension en [voyant comment l'opérateur post-incrément fonctionne] (https://stackoverflow.com/questions/484462/difference-between-i-and-i-in-a-loop). –

+0

@Karl: Dans votre commentaire, vous voulez 'auto newIt = std :: next (it)' (ou quelque chose d'autre.Ce que vous avez est l'incrémenter ainsi que l'attribution de la valeur incrémentée à newIt. –