Que se passe-t-il si vous appelez erase () sur un élément de carte tout en itérant du début à la fin?

133

Dans le code suivant, je parcourt une carte et teste si un élément doit être effacé. Est-il sûr d'effacer l'élément et de continuer à itérer ou dois-je collecter les clés dans un autre conteneur et faire une deuxième boucle pour appeler effacer ()?

map<string, SerialdMsg::SerialFunction_t>::iterator pm_it;
for (pm_it = port_map.begin(); pm_it != port_map.end(); pm_it++)
{
    if (pm_it->second == delete_this_id) {
        port_map.erase(pm_it->first);
    }
}

MISE À JOUR: Bien sûr, j'ai ensuite lu cette question que je ne pensais pas être liée mais répond à ma question.

Matthew Smith
la source
S'il vous plaît noter en question qui std::remove_ifne fonctionne pas avecstd:map
socketpair

Réponses:

183

C ++ 11

Ce problème a été corrigé dans C ++ 11 (ou l'effacement a été amélioré / rendu cohérent dans tous les types de conteneurs).
La méthode erase renvoie maintenant l'itérateur suivant.

auto pm_it = port_map.begin();
while(pm_it != port_map.end())
{
    if (pm_it->second == delete_this_id)
    {
        pm_it = port_map.erase(pm_it);
    }
    else
    {
        ++pm_it;
    }
}

C ++ 03

La suppression d'éléments dans une carte n'invalide aucun itérateur.
(à l'exception des itérateurs sur l'élément qui a été supprimé)

En fait, l'insertion ou la suppression n'invalide aucun des itérateurs:

Voir aussi cette réponse:
Mark Ransom Technique

Mais vous devez mettre à jour votre code:
dans votre code, vous incrémentez pm_it après avoir appelé erase. À ce stade, il est trop tard et est déjà invalidé.

map<string, SerialdMsg::SerialFunction_t>::iterator pm_it = port_map.begin();
while(pm_it != port_map.end())
{
    if (pm_it->second == delete_this_id)
    {
        port_map.erase(pm_it++);  // Use iterator.
                                  // Note the post increment.
                                  // Increments the iterator but returns the
                                  // original value for use by erase 
    }
    else
    {
        ++pm_it;           // Can use pre-increment in this case
                           // To make sure you have the efficient version
    }
}
Martin York
la source
L'ordre d'évaluation de l'incrément dans l'expression postfix est-il pm_it++garanti pour être exécuté avant l'entrée de la fonction?
David Rodríguez - dribeas
4
@David Rodríguez - dribeas: Oui. La norme garantit que toutes les expressions d'argument seront entièrement évaluées avant l'appel de la fonction. C'est le résultat du post-incrément qui est passé à la fonction d'effacement (). Donc oui, l'incrément de post de pm_it sera fait avant que erase () ne soit appelé.
Martin York
REMARQUE: Presque ligne pour ligne correspond à l'exemple de conteneur associatif dans l'élément 9 "Effective STL" de Scott Meyer
Ogre Psalm33
pour (auto pm_t = port_map.begin (); pm_it! = port_map.end ();) {...}
Andrey Syrokomskiy
4
@iboisver: Sur le vecteur. L'utilisation de erase () invalide tous les itérateurs du tableau après le point d'effacement (pas seulement la fin), c'est une propriété des Sequenceconteneurs. La propriété spéciale des Associativeconteneurs est que les itérateurs ne sont pas invalidés par effacement ou insertion (à moins qu'ils ne pointent sur l'élément qui a été effacé). Le vecteur et l'effacement des itérateurs sont traités en détail dans la question appropriée stackoverflow.com/a/3938847/14065
Martin York
12

Voici comment je fais ça ...

typedef map<string, string>   StringsMap;
typedef StringsMap::iterator  StrinsMapIterator;

StringsMap m_TheMap; // Your map, fill it up with data    

bool IsTheOneToDelete(string str)
{
     return true; // Add your deletion criteria logic here
}

void SelectiveDelete()
{
     StringsMapIter itBegin = m_TheMap.begin();
     StringsMapIter itEnd   = m_TheMap.end();
     StringsMapIter itTemp;

     while (itBegin != itEnd)
     {
          if (IsTheOneToDelete(itBegin->second)) // Criteria checking here
          {
               itTemp = itBegin;          // Keep a reference to the iter
               ++itBegin;                 // Advance in the map
               m_TheMap.erase(itTemp);    // Erase it !!!
          }
          else
               ++itBegin;                 // Just move on ...
     }
}
AlaaShaker
la source
Si vous supprimez également la fin du vecteur (itEnd), la dernière vérification (la condition while) sera effectuée par rapport à un itérateur invalidé (itEnd). Pas bon.
Agostino
1

Voici comment je le ferais, approximativement:

bool is_remove( pair<string, SerialdMsg::SerialFunction_t> val )
{
    return val.second == delete_this_id;
}

map<string, SerialdMsg::SerialFunction_t>::iterator new_end = 
    remove_if (port_map.begin( ), port_map.end( ), is_remove );

port_map.erase (new_end, port_map.end( ) );

Il y a quelque chose d'étrange dans

val.second == delete_this_id

mais je viens de le copier à partir de votre exemple de code.

Ravenspoint
la source