Problem mit ungültigem Iterator



  • fürs Löschen muss ich die std::functions vergleichen können und std::function lassen sich wohl nicht vergleichen (siehe diverse Threads im Internet). Die EventHandler Klasse beeinhaltet noch eine fortlaufende ID über die ich den Vergleich mache.

    Edit:
    http://stackoverflow.com/questions/3629835/why-is-stdfunction-not-equality-comparable

    Edit2: ich merke gerade .NET macht das wohl auch so:

    private void button1_Click(object sender, EventArgs e)
    {
    	button1.Click -= ev; // = button2_Click
    }
    
    private void button2_Click(object sender, EventArgs e)
    {
    
    }
    

    Beide Funktionen werden beim ersten Klicken ausgeführt, danach nur noch eine. Eigentlich aber nicht intuitiv die Lösung? Wenn ich einen EventHandler entferne, sollte er nicht nochmal aufgerufen werden können danach. Naja egal, dann mach ichs mit ner Löschlist/vector 😉

    greetz KN4CK3R



  • du könntest es auch so machen, dass du vor dem funktiosn aufruf dir einen temporären iterator erzeugst und den schon eine position weiter gehen lässt. und danach inkrementierst du deinen schleifen iterator nicht sondern weist ihm den temporären zu

    for (auto it = eventHandler.begin(); it != eventHandler.end(); ++it)
    {
        auto tmpIt = it++;
        it->handler();
        it = tmpIt;
    }
    

    iteratoren sind ja nicht die grössten und komplexesten objekte, von daher wird das auch nicht schlimm sein, die zuweisung immer zu machen anstatt einer einfachen inkrementierung



  • ohne es jetzt ausprobiert zu haben, der Code scheint Fehler zu enthalten.
    Meiner Meinung nach müsste das so aussehen oder?

    for (auto it = eventHandler.begin(); it != eventHandler.end(); )
    {
        auto tmpIt = it;
        it++;
        tmpIt->handler();
    }
    

    Edit: danke für den Hinweis, die Methode sieht am besten aus 🙂
    http://ideone.com/V7cpn

    greetz KN4CK3R



  • ups, ja stimmt jetzt wo dus sagst xD

    aber du hast erfasst was ich meinte, und mehr als ein beispiel sollte es nicht sein



  • Kann es sein, dass Du sowas wie boost::signals nachbaust?



  • Ich hab´ sowas Ähnliches gebaut, allerdings lege ich nicht die Funktionszeiger in einem Vektor ab, sondern sowas:

    struct EventCallback
    {
       bool Removed;
       FuncPtr Callback;
    };
    

    Während des "Feuerns" des Events wird der entsprechende Eintrag nicht aus dem Vektor entfernt, sondern nur das Removed Flag gesetzt. Am Ende der Funktion werden dann alle Einträge entfernt, deren Removed Flag gesetzt ist.
    Die Funktion zum Austragen eines Callbacks arbeitet in zwei Modi: Wenn sie aufgerufen wird, während Events abgefeuert werden, markiert sie den entsprechenden Eintrag zum Löschen, ansonsten entfernt sie ihn aus dem Vektor.
    Zuletzt musst du vorm Aufrufen der Callbacks noch prüfen, ob das Removed Flag gesetzt ist.



  • @Tachyon: boost::signals kenne ich nur vom Namen her, wie das dort gemacht wird, weiß ich nicht

    @DocShoe: das wäre meine Möglichkeit 2 gewesen, die Lösung von Skym0sh0 gefällt mir allerdings besser, weil sie ohne solch ein Flag auskommt.

    greetz KN4CK3R



  • ich hab das jetzt einmal testen können und musste feststellen, dass die Version doch nicht so gut ist, wie sie scheint. 😞
    Wenn das Event entfernt wird, auf das man it in der Zeile auto tmpit = it++ zeigen lässt, dann landet man wieder beim Anfangsproblem. :|
    Also dann wohl doch DocShoe's Lösungsansatz.

    greetz KN4CK3R


  • Mod

    KN4CK3R schrieb:

    ohne es jetzt ausprobiert zu haben, der Code scheint Fehler zu enthalten.
    Meiner Meinung nach müsste das so aussehen oder?

    for (auto it = eventHandler.begin(); it != eventHandler.end(); )
    {
        auto tmpIt = it;
        it++;
        tmpIt->handler();
    }
    

    Edit: danke für den Hinweis, die Methode sieht am besten aus 🙂
    http://ideone.com/V7cpn

    greetz KN4CK3R

    Setzt aber gültige Iteratoren nach dem erase voraus, funktioniert folglich nur mit Listen. Besser ist es, den Rückgabewert von erase zu verwenden.

    for (auto it = l.begin(); it != l.end();)
            {
                    cout << *it << endl;
                    x++ % 2 == 0 ? it = l.erase(it) : ++it;
            }
    


  • Das Problem ist, dass man beim Absetzen der Events keine Informationen darüber bekommt, dass sich jemand aus der Liste austrägt. Damit verlierst du den Rückgabewert von erase und kannst ihn nicht benutzen.



  • habs jetzt so gemacht:

    //die Remove Funktion setzt remove auf true
    for (auto it = eventHandler.begin(); it != eventHandler.end();)
    {
    	EventHandler &info = *it;
    	if (!info.remove)
    	{
    		info.eventHandler.GetFunction()(std::forward<T>(param)); //fire event
    	}
    	if (info.remove)
    	{
    		it = eventHandler.erase(it);
    	}
    	else
    	{
    		++it;
    	}
    }
    

    Dieser Version sollte failsafe sein auch wenn im ersten EventHandler alle anderen gelöscht werden würden. Auch das hinzufügen von neuen EventHandlern ist so abgedeckt.

    greetz KN4CK3R


Anmelden zum Antworten