std::unordered_map richtig mit Klassenobjekten füllen



  • Hallo,

    ich habe einen Code in dem ich eine map mit Objekten einer Klasse fülle. Die frage die sich mir stellt, was ist der richtige weg dazu. Und vor allem, wann wird eine Kopie erstellt und wann wird move benutzt. Ich denke das move ja besser sein würde, da ich das Objekt ja erst schon erstelle und von daher keine Kopie brauche. In dem Beispiel werden nach dem erstellen keine Änderungen an dem Objekt vorgenommen. In der Praxis wird vor dem ->add aber noch das Item mit aller Hand Informationen gefüttert.

    class ObjectList
    {
    protected:
    	typedef std::unordered_map<short, Item> ObjectListType;
    	ObjectListType list;
    
    public:
    	ObjectList();
    	ObjectListType &getList()
    	{
    		return this->list;
    	}
    	void add(short indedx, Item &item)
    	{
    		this->list[index] = item;
    	}
    
    };
    
    ObjectList *obj = new ObjectList();
    void initList()
    {
    	// Möglichkeit 1.
    	Item * item;
    	for (int i = 0; i < 25; i++)
    	{
    		// Möglichkeit 1.
    		// Muss hier noch ein ZeroMemory zwischen?
    		item = new Item(i);
    		obj->add(i, *item);
    		delete[] item;
    
    		// Möglichkeit 2.
    		// Finde ich irgendwie übersichlicher, aber was passiert ohne das delete?
    		// Warum gibt es einen Fehler wenn ich nur Item item2(); mache obwohl ich einen leeren constructor habe
    		Item item2(i);	
    		obj->add(i, item2);
    
    	}
    }
    

    Eine weitere Frage ist, wenn ich getList() nutzen möchte, wann wird eine Kopie der liste, und wann wirklich die Referenz zurück zu gegeben?



  • Hallo

    Hier gibt es ein paar Fehler.

    Zeile 31 führst du ein Array dellte auf einen "normalen" Zeiger durch.

    Zeile 36 wird als funktionsdeklaration angesehen, entferne die () und es wird funktionieren.

    Zeile 13 wenn du hier einen Movie möchtest, dann gebe es auch entsprechend mit an

    Und wegen deiner 2 Möglichkeit, wenn der Score verlassen wird, dann würde das lokale Objekt zerstört werden, was aber nicht dein Objekt in der Map betrifft, da entweder eine Kopie erzeugt wurde oder die Daten verschoben wurden. Diesen Weg würde ich auch bevorzugen.

    Und um ein Movie zu erzwingen, würde es noch die Funktion std::move() geben.

    Und zum allgemeinen, es wäre gut wenn du das nächste Mal bei Fehlern auch die vom Compiler mit angibst.

    Mfg Marco



  • Danke für deine schnelle Antwort.

    Ich nutzte derzeit Möglichkeit 2. Da gab es auch nie Fehler weshalb ich keine beigefügt habe.

    das std:move würde man simpel wie folgt verwenden?:

    void ObjectList::add(short index, Item &item)
    {
    	this->list[index] = std::move(item);
    }
    

    Und steht auch fest das nicht aus versehen das Objekt doch durch initList() noch zerstört wird nachdem es verschoben wurde?

    Noch die Nebenfrage wegen des zurückgeben der Liste und das verarbeiten...

    for (auto &it : obj->getList())
    	{
    		short otherNumber = rand() % 101 + 1;
    
    		/*
    		 * Diese schreibweise?
    		auto &item = it.second;
    		std::cout << "Item [" << it.first << "]\t(" << item.getNameID() << ") -> (" << otherNumber << ")\n";
    		item.changeNameID(otherNumber);
    		*/
    
    		// Oder doch diese schreibweise?
    		auto item = &it.second;
    		std::cout << "Item [" << it.first << "]\t(" << item->getNameID() << ") -> (" << otherNumber << ")\n";
    		item->changeNameID(otherNumber);
    	}
    

    Gibt es da irgend einen Unterschied bezüglich Speicherzugriff, Geschwindigkeit etc oder ist es so das man sich aussuchen kann ob man mit Punkt (.) oder Pfeil (->) arbeiten möchte?



  • MrSpoocy schrieb:

    das std:move würde man simpel wie folgt verwenden?:

    void ObjectList::add(short index, Item &item)
    {
    	this->list[index] = std::move(item);
    }
    

    Kann man so machen, würde ich aber nicht empfehlen. Wenn du das so machst ist beim Aufruf obj->add(i, item2); nicht offensichtlich, dass ein move stattfindet und eventuell will jemand nach dem add noch irgendwas mit item2 machen. Ich würde es so machen wie es die STL auch macht. Zwei Funktionen, eine für const references, wo es dann eine Kopie gibt, und eine für rvalue references. Würde dann irgendwie so aussehen:

    void ObjectList::add(short index, const Item &item)
    {
      this->list[index] = item;
    }
    
    void ObjectList::add(short index, Item &&item)
    {
      this->list[index] = std::move(item);
    }
    

    Wobei ich jetzt nicht garantieren will, dass dies wirklich die absolut beste und richtige Variante ist. Rvalue references sind schon ein sehr fortgeschrittenes Thema. Kann deine Item Klasse überhaupt von move semantics profitieren? Wenn nicht kann man sich das sowieso sparen.

    MrSpoocy schrieb:

    Und steht auch fest das nicht aus versehen das Objekt doch durch initList() noch zerstört wird nachdem es verschoben wurde?

    Ja das steht fest. Wenn du ein move machst kriegst du irgendein (vermutlich leeres) Objekt. Und man hat seinen move constructor und move assignment operator so zu erstellen, dass man wenigstens den Destruktor eines verschobenen Objekts aufrufen kann.

    MrSpoocy schrieb:

    Gibt es da irgend einen Unterschied bezüglich Speicherzugriff, Geschwindigkeit etc oder ist es so das man sich aussuchen kann ob man mit Punkt (.) oder Pfeil (->) arbeiten möchte?

    Einmal nutzt du Pointer und einmal eine Referenz. Hinter den Kulissen ist eine Referenz aber auch nicht viel mehr als ein besserer Pointer. Geschwindigkeit sollte von daher gleich sein. Ich würde die Variante mit Referenz bevorzugen. Ein Punkt ist ein Zeichen weniger als ein Pfeil und Operatorüberladung funktioniert dann auch noch.



  • Also in der Item-Klasse habe ich jetzt keine besonderen Konstruktor. Bin in C++ nicht so erfahren. Darum ist es auch oft echt verwirrend wann nun ein & oder * genutzt wird, warum das mal vorher dereferenziert werden muss und mal nicht.

    Sagt mal, mit

    Item &Klasse::add(Item &item)
    {
    return map[index] = item;
    }

    bekomme ich ja den Pointer auf das Element in der Liste zurück. Bleibt dieser Pointer auch wirklich immer vaild? Also auch wenn neue Elemente hinzukommen, gelöscht werden etc (mal abgesehen von genau dem Element auf den der Pointer zeigt).

    Und wie schaut es aus wenn das Element auf den der Pointer zeigt verändert wird, ist der Pointer dann immer noch gültig oder brauch man einen neuen?

    Hintergrund: In der einen Liste sind alle Item Objekte, in einer 2ten Liste sollen nur bestimmte Item Objekte sein, welche aber immer genau gleich zur 1ten Liste sind. Darum dachte ich mir, macht es ja sin, in der zweiten Liste einfach nur die verweise auf die Elemente in der 1ten Liste zu speichern. Bin mir aber nicht sicher ob das so ok ist.



  • MrSpoocy schrieb:

    Bin in C++ nicht so erfahren. Darum ist es auch oft echt verwirrend wann nun ein & oder * genutzt wird, warum das mal vorher dereferenziert werden muss und mal nicht.

    Wenn du einen Pointer hast, musst du dereferenzieren via *, um den Wert zu bekommen, andernfalls nicht. Wenn du dich da aber nicht so auskennt, solltest du vielleicht ein gutes Grundlagenbuch lesen.

    Sagt mal, mit

    Item &Klasse::add(Item &item)
    {
    return map[index] = item;
    }

    bekomme ich ja den Pointer auf das Element in der Liste zurück.

    Referenz, nicht Pointer.

    Bleibt dieser Pointer auch wirklich immer vaild? Also auch wenn neue Elemente hinzukommen, gelöscht werden etc (mal abgesehen von genau dem Element auf den der Pointer zeigt).

    Und wie schaut es aus wenn das Element auf den der Pointer zeigt verändert wird, ist der Pointer dann immer noch gültig oder brauch man einen neuen?

    Bleibt gültig:

    http://en.cppreference.com/w/cpp/container/unordered_map schrieb:

    References and pointers to either key or data stored in the container are only invalidated by erasing that element, even when the corresponding iterator is invalidated.



  • MrSpoocy schrieb:

    Hintergrund: In der einen Liste sind alle Item Objekte, in einer 2ten Liste sollen nur bestimmte Item Objekte sein, welche aber immer genau gleich zur 1ten Liste sind. Darum dachte ich mir, macht es ja sin, in der zweiten Liste einfach nur die verweise auf die Elemente in der 1ten Liste zu speichern. Bin mir aber nicht sicher ob das so ok ist.

    Klar kann man machen. Solltest du dann aber besser mit Pointern machen, statt mit Referenzen (mit std::reference_wrapper sollte es aber auch mit Referenzen gehen). Oder da du eh schon deine unordered_map hast könntest du in der zweiten Liste auch nur die Keys speichern, dann brauchst du dir keine sorge um ungültig gewordene Pointer machen. Warum nutzt du überhaupt unordered_map? Für den Code im Beispiel hätte ich eher ein std::vector genommen aber ich weiß ja nicht was du sonst noch an Code hast.



  • map nutze ich da ich den index (key) im Auge haben muss, also dieser ist besonders wichtig. Eine einfache vector liste reicht da leider nicht. Aber die Idee in der 2ten Liste nur die Key anstelle des Pointer zu speichern ist nicht so schlecht, nur ist nicht "bequemer" mit dem Pointer zu arbeiten? Weil dann kann ich ja direkt mit den Objekten arbeiten und muss nicht immer erst aus der ersten liste das Objekt laden.

    Um den Pointer anstelle der Referenz zu setzen mach ich derzeit folgendes (geht auch):

    typedef std::unordered_map<short, Item> ObjectListType;
    ObjectListType list;
    
    typedef std::unordered_map<short, Item*> PointerList;
    PointerList plist;
    
    Item &ObjectList::add(short index, const Item &item)
    {
    	return this->list[index] = item;
    }
    
    void RefList::set(short index, Item *item)
    {
    	this->plist[index] = item;
    }
    
    ObjectList *obj = new ObjectList();
    RefList *refList = new RefList();
    
    Item item;	
    Item *ref = &obj->add(index, item);
    refList->set(index, ref);
    

    Ist das so richtig?


Log in to reply