IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

 C++ Discussion :

std::map: erase dans une boucle


Sujet :

C++

  1. #1
    Membre actif
    Profil pro
    Inscrit en
    Février 2006
    Messages
    396
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Février 2006
    Messages : 396
    Points : 230
    Points
    230
    Par défaut std::map: erase dans une boucle
    Bonjour;

    J'ai ce code source qui permet de supprimer un élement dans une map:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    for(std::map<int, int>::iterator it = m.begin(); it!=m.end(); )
    {
    	if(it->first==1 || it->first==2)
    	{
    		m.erase(it++);
    	}else{
    		it++;
    	}
    }
    Je n'arrive pas à comprendre pourquoi le code source suivant ne fonctionne pas et est différent du premier:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    for(std::map<int, int>::iterator it = m.begin(); it!=m.end(); )
    {
    	if(it->first==1 || it->first==2)
    	{
    		m.erase(it);
    		it++;
    	}else{
    		it++;
    	}
    }
    Dans les deux cas, l'iterateur est incrémenté (++) après l'appel à erase, non ?

    Merci d'avance.

  2. #2
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    Le fait est que, dans le premier code, tu incrémente d'abord ton itérateur (donc, tu passes d'abord à l'élément suivant), et puis seulement, tu supprimes l'élément.

    C'est à dire que si la clé vaut 1 ou que si la clé vaut 2, tu prends la clé suivante (2,3 ou... ce qui suit la clé sur laquelle tu te trouves, de manière générale)

    Dans le second exemple, tu supprimes d'abord l'élément que tu as trouvé (celui dont la clé vaut 1 ou 2) et puis seulement tu passes à l'élément suivant.

    En fait ton premier code est plutôt équivalent à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    for(std::map<int, int>::iterator it = m.begin(); it!=m.end(); )
    {
    	if(it->first==1 || it->first==2)
    	{
    		it++;
    		m.erase(it);
    	}else{
    		it++;
    	}
    }
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Bonjour,

    je miserais plutôt sur un coup de bol pour que le premier code ne crash pas non plus.

    "par hasard" (ou pas ?) ton m.erase(it++); équivaudrait donc à it = m.erase(it);, qui est la bonne façon de supprimer un itérateur pendant son parcours.
    Toutes les méthodes de type erase sur itérateur retourne l'itérateur suivant. cf la doc.

    @Koala, ton code crashera à la boucle suivante, tu incrémentes it, le supprimes, puis... tu repasses par ->first qui est indéterminé.

    Cela dit, si c'est juste pour supprimer une clé, erase est surchargé dans le cas des maps pour prendre en paramètre une const key&
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    myMap.erase[1];
    myMap.erase[2];
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  4. #4
    Membre émérite

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Points : 2 252
    Points
    2 252
    Par défaut
    Le premier code de zenux n'est certainement pas un coup de bol, c'était la méthode recommandée (et la seule qui marche) pour effacer des élements d'une map dans une boucle for en C++03 !

    voir par exemple : http://stackoverflow.com/questions/8...e-iterating-it

    Depuis, En C++11, erase() a été corrigé pour retourner l'itérateur sur l'élément suivant, mais sur les vieux compilos on est toujours obligé d'utiliser l'astuce du m.erase(it++);

  5. #5
    Membre actif
    Profil pro
    Inscrit en
    Février 2006
    Messages
    396
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Février 2006
    Messages : 396
    Points : 230
    Points
    230
    Par défaut
    Merci pour vos réponses mais j'ai toujours un peu de mal à comprendre.
    Dans mon exemple, c'est bien les éléments ayant la clef 1 et 2 qui sont supprimés (et pas 2 et 3).

    La seul explication possible que je vois pour que le code source "m.erase(it++);" fonctionne est:

    1) La méthode 'erase' prend une copie de 'it'
    2) La variable 'it' est incrémentée
    3) La méthode 'erase' s'exécute sur base la variable 'it' copié précédement afin de supprimer le bon élément (et pas le suivant)

    Est-ce bien dans cet ordre que l'exécution fonctionne ?

  6. #6
    Membre émérite

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Points : 2 252
    Points
    2 252
    Par défaut
    Oui c'est exactement ça.
    Toute l'astuce repose sur le comportement de l'incrémentation par l'opérateur de postincrémentation++. L'implémentation de cet opérateur ressemble plus ou moins à :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    map_iterator map_iterator::operator++(int) //postincrémentation
    {	
       map_iterator copy_it= *this;
       ++*this;
       return copy_it;
    }
    il fait donc :
    1) Une copie de l'itérateur
    2) incrémente l'original
    3) Renvoie la copie

    C'est bien la copie non incrémentée qui est passé à erase().
    Très important, l'incrémentation de l'original se fait *avant* l'appel à erase() car si erase() se finissait avant l'incrémentation alors l'itérateur n'aurait plus été valide.

    Autant dire que c'est quand même bigrement subtil, en tout cas suffisamment pour surprendre koala et bousk ce qui n'est pas chose facile.
    (et je mets dans le lot hein, la première fois que je suis tombé sur un code de ce style j'étais persuadé qu'il devait y avoir un bug quelque part)

    Donc si tu n'as pas accès à un compilo récent (sur lequel il suffit de faire it = m.erase(it);) le mieux reste encore d'être explicite et de faire la copie à la main :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    if(it->first==1 || it->first==2)
    {
        std::map<int, int>::iterator it_to_erase = it;
        it++;
       m.erase(it_to_erase);
    }

  7. #7
    Membre actif
    Profil pro
    Inscrit en
    Février 2006
    Messages
    396
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Février 2006
    Messages : 396
    Points : 230
    Points
    230
    Par défaut
    Merci beaucoup pour cette explication.
    C'est maintenant très clair.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [Google Maps] Affichage dans une boucle
    Par astroma dans le forum APIs Google
    Réponses: 1
    Dernier message: 04/09/2012, 23h25
  2. Modifier une map/liste dans une boucle
    Par Invité dans le forum Groovy
    Réponses: 1
    Dernier message: 31/10/2011, 08h55
  3. Script map imprimante dans une boucle
    Par coobye dans le forum VBScript
    Réponses: 2
    Dernier message: 17/06/2011, 10h23
  4. [SimpleXML] Google Maps, Problème d'encoding dans une boucle
    Par yahn dans le forum Bibliothèques et frameworks
    Réponses: 1
    Dernier message: 23/09/2006, 19h40
  5. Pause dans une boucle
    Par HT dans le forum Langage
    Réponses: 4
    Dernier message: 03/06/2003, 08h52

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo