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 :

Comment éviter de manipuler un vector durant son itération ?


Sujet :

C++

  1. #1
    Membre expérimenté
    Profil pro
    Inscrit en
    Février 2004
    Messages
    1 824
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2004
    Messages : 1 824
    Points : 1 544
    Points
    1 544
    Par défaut Comment éviter de manipuler un vector durant son itération ?
    Bonjour à tous,

    Voilà un problème courant lorsque l'on implémente le pattern Observer. L'observable détient une liste d'écouteurs, il la parcours pour notifier un événement et un écouteur en réception de cet événement n'a plus besoin d'écouter. Il se retire alors de la liste ce qui provoque l'invalidation de l'itérateur toujours en cours d'itération.

    Un petit exemple pour être plus concret :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
     
    class IListener
    {
    friend class Observable;
    protected:
       virtual void OnEvent() = 0;
    }
     
    class Observable
    {
    public:
       void AddListener(IListener* pListener) {
           m_vecListeners.push_back(pListener);
       }
       void RemoveListener(IListener* pListener) {
          m_vecListeners.erase(std::remove(m_vecListeners.begin(), m_vecListeners.end(), pListener), m_vecListeners.end());
       }
     
    private:
       void NotifyEvent() {
         for(std::vector<IListener*>::iterator it = m_vecListeners.begin(); it != m_vecListeners.end(); ++it)
            (*it)->OnEvent();
       }
     
       std::vector<IListener*> m_vecListeners;
    };
     
    class Ecouteur : public IListener
    {
    public:
       Ecouteur() {
          m_observable.AddListener(this);
       }
     
    protected:
       virtual void OnEvent() {
          m_observable.RemoveListener(this);      
       }
     
    private:
       Observable m_observable;
    };
    Je ne sais pas si ce code compile, en tout cas il est pas propre mais c'est juste pour étayer le principe.

    Du coup là quand l'observable émet un événement, ça crash. Il y a des solutions mais qui contraignent à ce que l'écouteur doive connaître la gestion des événements de l'écouté pour l'utiliser d'une manière particulière, et ça c'est pas normal.

    Alors comment faire proprement ?

    Merci
    "Heureusement qu'il y avait mon nez, sinon je l'aurais pris en pleine gueule" Walter Spanghero

  2. #2
    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
    J'ai corrigé plusieurs de ces bugs il y a peu, la solution est toujours la même et revient à faire une copie des callbacks à appeler
    Eventuellement ne pas oublier les mutex si contexte multi-threaded
    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.

  3. #3
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 471
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 471
    Points : 6 110
    Points
    6 110
    Par défaut
    On pourrait implémenter la classe Observable comme ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    class Observable
    {
    public:
        void AddListener(IListener& pListener) {
            assert(m_vecListeners.end() == std::find(m_vecListeners.begin(), m_vecListeners.end(), &pListener));
            for(IListener*& refPtr : m_vecListeners) {
                if(refPtr == nullptr) {
                    refPtr = &pListener;
                    return;
                }
            }
            m_vecListeners.push_back(&pListener);
        }
        void RemoveListener(const IListener& pListener) {
            for(IListener*& refPtr : m_vecListeners) {
                if(refPtr == &pListener) {
                    refPtr = nullptr;
                    return;
                }
            }
        }
     
    private:
        void NotifyEvent() const {
            for(IListener* ptr : m_vecListeners) {
                if(ptr != nullptr) {
                    ptr->OnEvent();
                }
            }
        }
     
       std::vector<IListener*> m_vecListeners;
    };
    Alors, pendant l'appel à Observable::NotifyEvent, l'observateur pourrait appeler Observable::RemoveListener.
    Au passage, Observable::RemoveListener devient plus performant, même si c'est au détriment de Observable::AddListener.

    Par contre, si, pendant l'appel à Observable::NotifyEvent, on veut pouvoir appeler Observable::AddListener, alors il faudra en amont que Observable::NotifyEvent fasse une copie du vecteur.

    A part ça, ton code compile presque, à un caractère près :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class IListener
    {
    friend class Observable;
    protected:
       virtual void OnEvent() = 0;
    } // Il manque un point-virgule ici.

  4. #4
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    Comme disait je ne sais plus qui, ne pas avoir peur d'ajouter des niveaux d'indirection en cas de soucis

    Voici une modification possible de la classe Observable.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    class Observable
    {
    	struct entendu
    	{
    		IListener* m_listener;
    		bool m_ok;
    		entendu(IListener* l) : m_listener(l), m_ok(false) {}
    	};
     
    public:
    	void AddListener(IListener* pListener) {
    		m_vecListeners.push_back(entendu(pListener));
    	}
    	void RemoveListener(IListener* pListener) {
    		std::vector<entendu>::iterator it =
    			std::find_if(m_vecListeners.begin(), m_vecListeners.end(),
    				[pListener](entendu const & e) { return e.m_listener == pListener; });
    		if (it != m_vecListeners.end())
    			it->m_ok = true;
    	}
     
    private:
    	void NotifyEvent() {
    		for (std::vector<entendu>::iterator it = m_vecListeners.begin(); it != m_vecListeners.end(); ++it)
    			(*it).m_listener->OnEvent();
    		m_vecListeners.erase(
    			std::remove_if(m_vecListeners.begin(), m_vecListeners.end(),
    				[](entendu const & e) { return e.m_ok == true; }),
    			m_vecListeners.end());
    	}
     
    	std::vector<entendu> m_vecListeners;
    };

Discussions similaires

  1. Réponses: 1
    Dernier message: 25/01/2013, 23h11
  2. Comment éviter d'avoir son domaine dans la blacklist
    Par vg-matrix dans le forum Sécurité
    Réponses: 1
    Dernier message: 08/09/2011, 18h41
  3. Réponses: 14
    Dernier message: 07/02/2008, 16h55
  4. [eclipse 2.1][compilation] Comment éviter...
    Par ftrifiro dans le forum Eclipse Java
    Réponses: 3
    Dernier message: 29/06/2004, 16h16
  5. comment tester si une fonction fait bien son travail
    Par access dans le forum Requêtes
    Réponses: 1
    Dernier message: 24/11/2003, 15h46

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