Vergleich mit STL map fügt zusätzlichen leeren Eintrag in map hinzu



  • Hallo in die Runde,

    ich bin gerade in einem Projekt über einen sehr interessanten Fehler im Vergleich einer STL map mit einem double Wert gestolpert.
    Ich habe das ganze einmal auf ein minimales Beispiel heruntergebrochen und komme nun zu dem Schluss, dass in der Implementierung des operators [] für die STL map irgendetwas schief laufen muss. Da mir google und der Debugger bis jetzt leider nicht weiter geholfen haben, stelle ich die Frage nun an euch : Was läuft hier schief? :

    Ausgangspunkt ist der folgende Programmcode :

    #include <iostream>
    #include <map>
    #include <string>
    
    using namespace std;
    
    int main() {
        map<string,double> data;
        data["GPS_Longitute"] = 1.0;
        data["GPS_Latitute"] = 2.0;
    
       for (auto& x : data) {
            cout << "[" << x.first << " ==> " << x.second << "]" << endl;
        }
    
        cout << "--------------------------------" << endl;
    
        if (data["GPS_Longitude"] >= 1000.0 ) {
           cout << "bla" << endl;
        }
    
        for (auto& x : data) {
            cout << "[" << x.first << " ==> " << x.second << "]" << endl;
        }
    
    }
    
    

    Ich lege eine map<string,double> and und füge dann zwei neue Werte hinzu.
    Wenn ich diese Map nun durchlaufe und alle Werte ausgebe, werden zwei Werte ausgegeben.
    Daraufhin führe ich einen Vergleich mit der map aus. Hier bei scheint unerheblich zu sein, womit ich vergleiche oder welche Operatoren (== >= <= ... ) ich zum Vergleichen nutze.
    Anschließend, (nach dem Vergleich) gebe ich erneut alle Werte meiner map aus. Nun erhalte ich jedoch nicht zwei sondern drei Ausgaben.

    [GPS_Latitute ==> 2]
    [GPS_Longitute ==> 1]
    --------------------------------
    [GPS_Latitute ==> 2]
    [GPS_Longitude ==> 0]
    [GPS_Longitute ==> 1]
    

    Nach einigem Debuggen sieht es für mich momentan so aus, als wenn in der Datei stl_map.h in der Implemetation des operators [] beim Suchen des Wertes für "GPS_Longitude" ein neuer Wert in der map angelegt wird und dieser anschließend nicht wieder gelöscht wird.

    #if __cplusplus >= 201103L
          mapped_type&
          operator[](key_type&& __k)
          {
    	// concept requirements
    	__glibcxx_function_requires(_DefaultConstructibleConcept<mapped_type>)
    
    	iterator __i = lower_bound(__k);
    	// __i->first is greater than or equivalent to __k.
    	if (__i == end() || key_comp()(__k, (*__i).first))
    	  __i = _M_t._M_emplace_hint_unique(__i, std::piecewise_construct,
    					std::forward_as_tuple(std::move(__k)),
    					std::tuple<>());
    	return (*__i).second;
          }
    #endif
    

    Soweit ich weiß, ist der operator [] so implementiert, dass der übergebene string als neuer Eintrag in der Map angelegt wird, falls er noch nicht vorhanden sein sollte. Allerdings verstehe ich in meinem Fall momentan leider nicht, wieso er denkt, dass mein Eintrag noch nicht vorhanden ist. Soweit ich es nachvollziehen konnte liegt der Fehler irgendwo im Aufruf von

    __i = _M_t._M_emplace_hint_unique(__i, std::piecewise_construct,
    					std::forward_as_tuple(std::move(__k)),
    					std::tuple<>());
    

    versteckt. Ich habe auch versucht tiefer rein zu schauen und es scheint so als wenn irgendwo in einem Iterator in der stl_tree.h etwas schief läuft. Nur hier bin ich dann irgendwann Kompetenz-technisch ausgestiegen, weil der STL code für mich sehr unleserlich / unverständlich war 😞

    Um auszuschließen, dass es an meinem speziellen Iterator in der vorherigen Schleife liegt, habe ich das Ganze auch noch einmal mit einer einfacheren Schleife probiert.

    #include <iostream>
    #include <map>
    #include <string>
    
    using namespace std;
    
    int main() {
        map<string,double> data;
        data["GPS_Longitute"] = 1.0;
        data["GPS_Latitute"] = 2.0;
    
        for(map<string,double>::iterator mapIter = data.begin(); mapIter != data.end(); ++mapIter) {
            cout << "[" << mapIter->first << " ==> " << mapIter->second << "]" << endl;
        }
    
        cout << "--------------------------------" << endl;
    
        if (data["GPS_Longitude"] >= 1000.0 ) {
           cout << "bla" << endl;
        }
    
        for(map<string,double>::iterator mapIter = data.begin(); mapIter != data.end(); ++mapIter) {
            cout << "[" << mapIter->first << " ==> " << mapIter->second << "]" << endl;
        }
    
    
    }
    
    

    Dies führte jedoch zu genau dem gleichen Ergebnis.
    Kann mir jemand von euch bei diesem Problem helfen, hat evtl. einen Tipp oder einen Gedankenansatz was hier schief läuft? Vlt. nutze ich die STL Funktionen auch einfach falsch und produziere dadurch den Fehler. Über irgendwelche Hilfe würde ich mich sehr freuen.

    Gruß,
    mezorian



  • Der operator[] fügt ein Default-konstruiertes Element hinzu. Das ist so beabsichtigt, denn der Operator gibt eine Referenz auf ein Element zurück, u.a. damit man data[bla] = blub; schreiben kann.

    Wenn du das nicht willst, musst du find benutzen.



  • "GPS_Longitude" != "GPS_Longitute"



  • Hallo @Bashar , hallo @Caligulaminus

    vielen Dank für eure Antworten.
    Das es sich hierbei einfach um einen typo meinerseits handelt, habe ich tatsächlich nun erfolgreich einen halben Tag lang nicht bemerkt 😞
    Aber vielen lieben Dank für den Hinweis. Echt ein bisschen peinlich 😃
    Aber naja nachdem nun an beiden Stellen Longitude mit einem d steht, funktioniert es natürlich..

    Trotzdem vielen Dank auch für die Erklärung des Unterschiedes zwischen [] und find. Ich habe das dann auch gleich mal ausprobiert.

    Vielen lieben Dank für die nette, kompetente und unfassbar schnelle Unterstützung 🙂

    mezorian

    P.S. anbei der Code mit find:

    #include <iostream>
    #include <map>
    #include <string>
    
    using namespace std;
    
    int main() {
        map<string,double> data;
        data["GPS_Longitude"] = 1.0;
        data["GPS_Latitude"] = 2.0;
    
        for (auto& x : data) {
            cout << "[" << x.first << " ==> " << x.second << "]" << endl;
        }
    
        cout << "--------------------------------" << endl;
    
        if (data.find("GPS_Longitude")->second >= 1000.0 ) {
           cout << "bla" << endl;
        }
    
        for (auto& x : data) {
            cout << "[" << x.first << " ==> " << x.second << "]" << endl;
        }
    
    }
    

    mit Ausgabe

    [GPS_Latitude ==> 2]
    [GPS_Longitude ==> 1]
    --------------------------------
    [GPS_Latitude ==> 2]
    [GPS_Longitude ==> 1]
    


  • Bei find solltest du aber zuerst den Rückgabewert prüfen, bevor du darauf zugreifst (sonst gibt es evtl. einen Speicherzugriffsfehler):

    auto it = data.find("GPS_Longitude");
    if (it != data.end())
    {
       if (it->second >= 1000.0)
         // ...
    }
    


  • Daher verwende ich den []-Operator bei den Maps nie. Zwar muss man mit einer Kombination aus find und emplace mehr Quellcode schreiben, aber man reduziert die Fehlerquote.

    Warum verwendest du eigentlich für zwei Key-Value-Paare eine Map? Hat das irgendeinen Hintergrund? Schreit eigentlich nach einer klassischen Klasse mit zwei Membern.



  • @It0101 sagte in Vergleich mit STL map fügt zusätzlichen leeren Eintrag in map hinzu:

    Daher verwende ich den []-Operator bei den Maps nie. Zwar muss man mit einer Kombination aus find und emplace mehr Quellcode schreiben, aber man reduziert die Fehlerquote.

    Hm... ich freue mich darüber, wenn man mit [] automatisch Einträge erzeugt. Insbesondere wird z.B. Code bei einer map einer map viel komplizierter, wenn man das jeweils manuell machen müsste. Wenn man wirklich nur nachgucken will und weiß, dass der Eintrag drin ist, dann nehme ich at statt find. Mein größter map Usecase ist, glaube ich, map<string, int>, wo ich irgendwas aufsummieren will. Und da ist [] unglaublich praktisch. Bin jedes mal genervt, wenn ich in Python mit dicts arbeite. Es widerstebt mir immer, sowas zu schreiben:

    if key not in map:
       map[key] = 1
    else:
      map[key] += 1
    
    # oder
    
    map.setdefault(key, 0)
    map[key] += 1
    

    (ja, man kann dann ein defaultdict nehmen, ich weiß)

    In C++ könntest du ja sowas in der folgenden Art machen (ob das ne gute Idee ist...):

    #include <map>
    
    template <
        typename Key,
        typename T,
        typename Compare = std::less<Key>,
        typename Allocator = std::allocator<std::pair<const Key, T> >
    >
    class NoAutovivificationMap :
        public std::map<Key, T, Compare, Allocator> {
    public:
        T& operator[](const Key &key) {
            return this->at(key);
        }
    };
    
    int main() {
        NoAutovivificationMap<int, int> m;
        m.insert({0, 42});
        return m[0];
    }
    

    Damit kannst du dem operator[] auch eine const-Version geben.



  • Naja ich verwende halt oft die Inplace-Konstruktion mit std::unordered_map::emplace, wo ich dann auch noch die Konstruktorparameter an die emplace-Funktion übergeben kann und dadurch das Objekt noncopyable machen kann, weil es eben weder gemoved noch kopiert wird sondern inplace-konstruiert wird.

    Bei nur zwei oder drei potentiellen Key-Value-Paaren ( wie hier in dem Beispiel ) verwende ich meist sogar gar keine Map, weil es Performance-Unsinn ist. Da nehm ich dann lieber std::vector mit std::find_if aus dem <algorithm>. Auch wenn es mehr Schreibarbeit ist.


Log in to reply