std::map: Iterator validity vs. pointer validity



  • Hallo zusammen,

    eine Klasse Central dient als zentraler Pool von Objekten vom Typ V. Sie speichert diese Objekte in einem privaten Member std::map<string, V>. Zum Erzeugen eines neuen V-Objekts stellt Central die Methode makeV(...) bereit. Sie platziert ein neues V in der map und gibt einen Pointer auf dieses map-Element zurück. Mittels der Methode processAllV() wird irgendeine Operation auf allen Objekten im Pool ausgeführt:

    class Central
    {
    public:
        static V* makeV(const string& a_id)
        {
            pool_.emplace(pair<string, V>(a_id, V()));
            return &(pool_.at(a_id));
        }
    
        static void processAllV()
        {
            for(pair<const string, V>& elem : pool_)
            {
                elem.second.doSmthn();
            }
            return;
        }
    
    private:
        static map<string, V> pool_;
    };
    

    Andere Klassen nutzen jetzt Central, um V-Objekte zu erzeugen. Sie manipulieren diese per Pointer:

    void SomeOtherClass::setupMyV()
    {
        myV_ = Central::makeV("from_some_other_class");   // myV_ vom Typ V*, Member von SomeOtherClass
        return;
    }
    
    void SomeOtherClass::manipulateMyV()
    {
        myV_->transform(42);
        return;
    }
    

    Nun sagt cppreference z. B. zu map::emplace(): "No iterators or references are invalidated."

    Gilt das auch für V*, also Pointer auf die map-Values?

    Ich habe es mit einem kurzen Beispiel getestet:

    #include <iostream>
    #include <map>
    #include <string>
    using namespace std;
    
    int main()
    {
        map<string, string> map_;
    
        map_.insert({"hello1", "hello1"});
        map_.insert({"hello2", "hello2"});
        map_.insert({"hello4", "hello4"});
    
        string* p1 = &(map_.at("hello1"));
        string* p2 = &(map_.at("hello2"));
        string* p4 = &(map_.at("hello4"));
    
        cout << *p1 << " at " << p1 << endl;
        cout << *p2 << " at " << p2 << endl;
        cout << *p4 << " at " << p4 << endl;
        for(pair<const string, string>& p : map_)
        {
            cout << p.first << "/" << p.second << " at " << &(p.second) << endl;
        }
    
        map_.emplace(pair<string, string>("hello", "hello"));
        map_.emplace(pair<string, string>("hello3", "hello3"));
        map_.emplace(pair<string, string>("hello3a", "hello3a"));
        map_.emplace(pair<string, string>("hello3b", "hello3b"));
        map_.emplace(pair<string, string>("hello3c", "hello3c"));
    
        cout << *p1 << " at " << p1 << endl;
        cout << *p2 << " at " << p2 << endl;
        cout << *p4 << " at " << p4 << endl;
        for(pair<const string, string>& p : map_)
        {
            cout << p.first << "/" << p.second << " at " << &(p.second) << endl;
        }
    
        return 0;
    
    }
    

    Die Pointer bleiben valide (ändern sich nicht), wie eigentlich erwartet. Aber das ist ja nunmal kein Beweis.

    Hier sagt einer: "the /iterator/ is stable, but keeping an iterator stable doesn't imply keeping the pointer stable (you could imagine a garbage collector making sure every iterator trace the data, but not pointers)."
    Habe aber auch schon gelesen, das gälte genauso für Pointer.

    Motivation: Ich habe eine map<string, Texture> um alle möglichen Bitmap-Grafiken zentral vorzuhalten. Ab und an erscheinen Texturen, die überhaupt nicht zum Rendern vorgemerkt waren, und ich überlege, ob unter meinen Pointern die Daten umherwandern.


  • Mod

    Wenn da von der Gültigkeit von Referenzen gesprochen wird, sind Pointer mit gemeint. Ich weiß nicht warum, aber der Standard (von wo der cppreference-Autor das übernommen haben wird) spricht beim emplace auch nur von Iteratoren und Referenzen, während anderswo auch Pointer mit aufgezählt werden. Vermutlich weil viele unterschiedliche Autoren, teils Jahrzehnte auseinander. Vielleicht steht auch irgendwo eine Begriffserklärung, dass bei der Definition der Library das Wort "reference" auch "pointer" beinhaltet, aber ich werde jetzt nicht den ganzen Standard danach durchforsten.

    Dass die Gültigkeit von Iteratoren und Referenzen/Pointern hingegen nicht unbedingt gleich ist, hast du ja schon selber anhand eines Beispiels erklärt. Daher sind die auch immer explizit genannt, und wenn eines von beiden fehlt, dann ist das auch wichtig.



  • @SeppJ sagte in std:🗺 Iterator validity vs. pointer validity:

    Wenn da von der Gültigkeit von Referenzen gesprochen wird, sind Pointer mit gemeint.

    Danke. In dem Fall liegt mein Problem woanders und ich kann das als Fehlerquelle ausschließen.


  • Mod

    Du könntest auch dein Problem schildern, und danach fragen, woran es liegt 🙂

    PS: Also ich meine damit eine detailliertere, technische Problembeschreibung mit echtem Code, nicht nur "da kommen falsche Texturen"



  • @cpr sagte in std:🗺 Iterator validity vs. pointer validity:

        pool_.emplace(pair<string, V>(a_id, V()));
        return &(pool_.at(a_id));
    

    Noch was anderes:
    Warum wird der Rückgabewert von emplace verworfen? Emplace returnt dir doch schon den Wert, den du danach nochmals nachguckst. Bei emplace kannst du auch direkt pool.emplace(a_id, V()) schreiben. Allerdings wird so immer ein V konstruiert, egal ob es schon vorhanden war oder nicht. Auch hier hätte ich erwartet, dass du den Rückgabewert abfragst. makeV klingt so, als würde es immer ein neues V anlegen, wobei es tatsächlich auch bestehende zurückgibt, also wäre vielleicht get_or_create_V korrekter.

    Noch eine andere Frage: warum ist die map in in deiner Central-Klasse static? Ist das nötig? Wäre nicht viel besser, wenn deine App nur ein Central-Objekt hätte, das dafür aber keine static-Member hat? Ich habe bei so viel static immer etwas Bauchweh. Central hört sich vom Namen her nach einer God-Klasse an. Ich hoffe, dass das nur dem Beispiel geschuldet ist.



  • @wob sagte in std:🗺 Iterator validity vs. pointer validity:

    Warum wird der Rückgabewert von emplace verworfen? Emplace returnt dir doch schon den Wert, den du danach nochmals nachguckst.

    Danke!

    @wob sagte in std:🗺 Iterator validity vs. pointer validity:

    Bei emplace kannst du auch direkt pool.emplace(a_id, V()) schreiben

    Ich probier's aus - zumindest mit pool.emplace({a_id, V()}) meine ich, hätte es Probleme gegeben, da isser nicht von selbst drauf gekommen, welche Datentypen das pair haben sollte... kann mich täuschen

    @wob sagte in std:🗺 Iterator validity vs. pointer validity:

    Allerdings wird so immer ein V konstruiert, egal ob es schon vorhanden war oder nicht.

    Richtig, im realen Code (ich weiss, den hätte ich auch posten sollen) wird vorher abgefragt, ob's schon existiert (wenn ja wäre das eine Fehlbenutzung)

    @wob sagte in std:🗺 Iterator validity vs. pointer validity:

    Wäre nicht viel besser, wenn deine App nur ein Central-Objekt hätte, das dafür aber keine static-Member hat?

    Stimmt - architektonisch eindeutig besser.

    Danke für die Tipps. Für konkreten Code muss ich erstmal ein kurzes nachvollziehbares Extrakt generieren - kann dauern, wird heute wohl leider nix mehr, sorry.

    Update: Die Sache ist stattdessen ein Problem mit meiner Verwendung von SDL2, bei Gelegenheit werde ich im entsprechenden Unterforum einen Thread öffnen. Der Thread hier kann geschlossen werden, danke allerseits.

    Update 2: Problem war trivial. Ich wollte aus einer SDL_Texture, die ein ganzes Tileset enthält, die einzelnen Tiles in eigene kleine SDL_Textures kopieren. Habe aber vergessen, nach jedem Tile auch SDL_RenderClear(SDL_Renderer*) aufzurufen, weshalb die Texturen dann letztlich aufeinandergestapelt rauskamen.


Anmelden zum Antworten