web-dev-qa-db-fra.com

Comment supprimer d'une carte en la réitérant?

Comment puis-je supprimer d'une carte tout en l'itérant? comme:

std::map<K, V> map;
for(auto i : map)
    if(needs_removing(i))
        // remove it from the map

Si j'utilise map.erase, les itérateurs seront invalidés

137
Dani

Idiome standard d'effacement de conteneur associatif:

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 boucle for 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 dans lesquelles nous nous soucions uniquement des éléments. La syntaxe de la RBFL le clarifie en ne même pas exposer le conteneur à l'intérieur du corps de la boucle.

Modifier. Pre-C++ 11, vous ne pouviez pas effacer les itérateurs constants. 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 incompatible avec la constance de l'élément. Par analogie, il a toujours été parfaitement légitime de delete pp est un pointeur sur une constante. La constance ne limite pas la durée de vie; Les valeurs const en C++ peuvent toujours arrêter d'exister.

221
Kerrek SB

Personnellement, je préfère ce modèle 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:

  • l'incrémenteur de boucle for est logique en tant qu'incrémenteur;
  • l'opération d'effacement est un effacement simple, plutôt que d'être mélangée avec une logique d'incrémentation;
  • après la première ligne du corps de la boucle, la signification de it et next_it reste fixe tout au long de l'itération, ce qui vous permet d'ajouter facilement des instructions supplémentaires les renvoyant sans scrupule si elles fonctionneront comme prévu (sauf que vous ne pouvez pas utiliser it après en l'effaçant).
14
D Coetzee

En bref "Comment puis-je supprimer d'une carte tout en l'itérant?"

  • Avec l'ancienne carte impl: vous ne pouvez pas
  • Avec la nouvelle carte impl: presque comme l'a suggéré @KerrekSB. Mais il y a quelques problèmes de syntaxe dans ce qu'il a posté.

De GCC map impl (note 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;
}

impressions:

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.
4
Kashyap

Assez triste, hein? La façon dont je le fais habituellement est de construire un conteneur d'itérateurs au lieu de le 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)
}
3
Michael Daum

En supposant que C++ 11, voici un corps de boucle monocouche, si cela correspond à 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:

  • Affiche le type déclaré (Map::const_iterator) lorsque cela est possible/commode, en utilisant auto.
  • Utilisez using pour les types de modèle, afin de faciliter la lecture/maintenance des types auxiliaires (Map::const_iterator).
0
Matt