web-dev-qa-db-fra.com

Comment supprimer des éléments d'une std :: map avec un itérateur?

Je voudrais parcourir un std::map et supprimer les éléments en fonction de leur contenu. Comment cela serait-il le mieux possible?

57
user542687

Si vous disposez d'un compilateur compatible C++ 11, voici un moyen simple de le faire:

std::map<K, V>::iterator itr = myMap.begin();
while (itr != myMap.end()) {
    if (ShouldDelete(*itr)) {
       itr = myMap.erase(itr);
    } else {
       ++itr;
    }
}

L'idée est de faire avancer l'itérateur du début du conteneur jusqu'à la fin, en vérifiant à chaque étape si la paire clé/valeur actuelle doit être supprimée. Si tel est le cas, nous supprimons l'élément itéré sur en utilisant la fonction membre erase, qui renvoie ensuite un itérateur à l'élément suivant dans la carte. Sinon, nous faisons avancer l'itérateur normalement.

Si vous n'avez pas de compilateur compatible C++ 11 ou que vous travaillez avec une base de code plus ancienne, les choses sont un peu plus compliquées. Avant C++ 11, la fonction membre erase ne renvoyait pas un itérateur à l'élément suivant dans la carte. Cela signifie que pour supprimer un élément pendant l'itération, vous devez utiliser une danse en trois parties:

  1. Copiez l'itérateur actuel.
  2. Avance l'itérateur actuel vers l'élément suivant.
  3. Appelez erase sur la copie de l'ancien itérateur.

Ceci est montré ici:

std::map<K, V>::iterator itr = myMap.begin();
while (itr != myMap.end()) {
    if (ShouldDelete(*itr)) {
       std::map<K, V>::iterator toErase = itr;
       ++itr;
       myMap.erase(toErase);
    } else {
       ++itr;
    }
}

Ce processus était nécessaire car si vous venez d'appeler erase sur l'itérateur, vous le feriez invalider, ce qui signifie que des opérations telles que l'incrémentation et la décrémentation entraîneraient un comportement indéfini. Le code ci-dessus contourne cela en configurant une copie de l'itérateur, en faisant avancer itr pour qu'il se trouve à l'élément suivant, puis en effaçant la copie temporaire de l'itérateur.

En utilisant une astuce intelligente, il est possible de réduire ce code au détriment de la lisibilité. Le modèle suivant est commun dans les anciens codes C++, mais n'est pas nécessaire dans C++ 11:

std::map<K, V>::iterator itr = myMap.begin();
while (itr != myMap.end()) {
    if (ShouldDelete(*itr)) {
       myMap.erase(itr++);  // <--- Note the post-increment!
    } else {
       ++itr;
    }
}

L'utilisation de l'opérateur de post-incrémentation ici est un moyen intelligent de faire une copie de l'ancien itérateur (rappelez-vous qu'un opérateur postfix ++ renvoie une copie de la valeur de l'itérateur d'origine) tout en faisant avancer l'ancien itérateur.

101
templatetypedef
for(MyMap::iterator it = mymap.begin(); it!=mymap.end(); ) {
  if(mycondition(it))
    it = mymap.erase(it);
  else
    it++;
}

edit: semble que cela ne fonctionne que dans MSVC

edit2: en c ++ 0x cela fonctionne aussi pour les conteneurs associatifs

8
Timo


C'est une façon simple:

    int value_to_delete( 2 );
    for( std::map<int, int>::iterator i = mm.begin(); i != mm.end(); ) {
        if( i->second != value_to_delete ) {
            mm.erase( i++ ); // advance before iterator become invalid
        }
        else {
            ++i;
        }
    }
7
Chan