unique_ptr aus map auslesen und wo ist make_unique?
-
wob schrieb:
Temi (temp) schrieb:
Da hast du Recht, aber dann müsste ich die daraus ausgelesenen Zeigertypen (Item, Location, Person, ...) jeweils auf den korrekten Typen casten. Bei der generischen Variante entfällt das.
Aber du hast doch trotzdem in deinem Code einen dynamic_cast drin! Warum? Du sagst doch gerade, das sei jetzt generisch.
Gibt es eine andere Möglichkeit?
Ich möchte dem Catalog<T> Objektzeiger auf Objekte übergeben, die die Schnittstelle IObject (oder genauer IHasId) implementieren und beim Abrufen den gespeicherten Typzeiger zurück erhalten => T* find(ulong id).
Natürlich könnte ich sowas machen: void add(ulong id, IHasId object). Damit braucht der Katalog von dem Zugriff auf die Id nichts zu wissen, ABER das muss doch auch in C++ machbar sein. In C# ist das einfach:
public class Catalog<T> where T : IHasId
-
Wie wob schon schrieb, du brauchst hier überhaupt kein Template:
class Catalog { public: Catalog() {}; void add(IHasId* object); // noch mehr Kram private: std::unordered_map<ulong, std::unique_ptr<IHasId>> map; };
Und selbst in deiner Template-Variante reicht ein:
object->getId() // ohne cast!
Jede Klasse, welche dann eine
ulong getId()
bietet, kann dann benutzt werden.
C++ templates sind unabhängig von irgend einer Klassenhierarchie.
-
Th69 schrieb:
C++ templates sind unabhängig von irgend einer Klassenhierarchie.
Hier bist du einem missverständnis aufgesessen.
Die where clause in einem C# Generic bewirkt eine Einschränkung mit welchen Typen das Generic verwendet werden kann. (generic type constraint)
In dem beispiel von Temi (temp)public class Catalog<T> where T : IHasId
kann die Generic klasse nur verwendet werden wenn "T" von Typ IHashId ist bzw. eine von dieser klasse/interface abgeleiteter Typ ist
-
Ich würde gerne den Typ rausbekommen, den ich reingesteckt habe, also:
class Item : public IHasId, public IItem { //.. } class Location : public IHasId, public ILocation { //.. } itemCatalog.add(new Item); locationCatalog.add(new Location); item* i = itemCatalog.find(id); location* l = locationCatalog.find(42);
Von einer Klasse die nur "IHasId" aufnimmt, bekomme ich auch nur IHasId zurück, oder etwa nicht?
Angenehm wäre dabei vielleicht, dass nur ein Katalog alle Typen aufnehmen könnte, aber dann müsste ich nachdem ich meinen Zeiger zurück habe casten.
ILocation* l = dynamic_cast<ILocation*>(completeCatalog.find(42))
Das ist aber ziemlich unsicher, weil ich ja u.U. nicht genau weiß, was hinter der Id "42" gelistet ist. Dazu bräuchte ich dann zumindest ein (C#):
if (completeCatalog.find(42) is ILocation)
Soweit ich gesehen habe geht das aber bei C++ nicht, oder?
Eine Möglichkeit wäre noch IHasId um eine Angabe des Types a la "enum ObjectType..." zu erweitern, um den korrekten Typ zu ermitteln.
temi und temi (temp) bin übrigens beides ich, auf der Arbeit hatte ich meine Anmeldedaten nicht parat.
Danke für die anregende Diskussion,
temi
-
Mir fällt gerade ein, ich könnte natürlich auch für jeden Typen eine eigene Suchmethode anbieten:
class Catalog { public: Catalog() {}; void add(IHasId* object); // noch mehr Kram IItem* findItem(ulong id); ILocation* findLocation(ulong id); // usw. :) private: std::unordered_map<ulong, std::unique_ptr<IHasId>> map; };
So richtig hübsch ist das aber auch nicht.
Überladen einer Methode mit unterschiedlichen Rückgabewerten in der Signatur ist soweit ich gelesen habe ja nicht möglich?
Aber das müsste sich doch irgendwie machen lassen, oder?
auto l = catalog.find(42).asLocation(); // Typprüfung intern!?
-
wob schrieb:
2. Wenn du ein Template nimmst, dann könnte man mit deinem Code einen Katalog von int machen und zur Laufzeit(!) bekommst du dann eine Nullpointer-Dereferenzierung, weil int* in Object* gedynamiccastet wird und das ergibt nullptr).
Hab ich grad ausprobiert, das kompiliert nicht.
Catalog<int> c2; c2.add(new int{42});
Fehler: cannot dynamic_cast 'object' (of type 'int*') to type 'class tad::Object*' (source is not a pointer to class)
map.insert(std::make_pair<ulong, std::unique_ptr<T>>(dynamic_cast<Object*>(object)->getId(), std::unique_ptr<T>(object)));
-
firefly schrieb:
Th69 schrieb:
C++ templates sind unabhängig von irgend einer Klassenhierarchie.
Hier bist du einem missverständnis aufgesessen.
...Hä??? Ich weiß, wie C#-Generics funktionieren - und ich schrieb, daß eben bei den C++ Templates der zu verwendende Typ nicht von irgendeiner Klassenhierarchie (wie eben bei den C# Generics) abhängig ist.
Und @temi:
template<class T> T* Catalog<T>::find(ulong id) const { return map.at(id).get(); // 1. dies funktioniert auto const &it = map.at(id); // 2. so funktioniert das! return it != map.end() ? it.get() : nullptr; return map[id].get(); // 3. das funktioniert auch nicht - kann auch nicht, denn dies funktioniert nur bei nichtkonstanten Funktionen // (denn bei Bedarf wird hier ein Element der map hinzugefügt). }
-
Th69 schrieb:
firefly schrieb:
Th69 schrieb:
C++ templates sind unabhängig von irgend einer Klassenhierarchie.
Hier bist du einem missverständnis aufgesessen.
...Hä??? Ich weiß, wie C#-Generics funktionieren - und ich schrieb, daß eben bei den C++ Templates der zu verwendende Typ nicht von irgendeiner Klassenhierarchie (wie eben bei den C# Generics) abhängig ist.
Deine Antwort klang aber anders, sorry wenn ich dich falsch verstanden haben sollte
-
Ich hab es jetzt so gemacht. Damit ist der Iterator auch wirklich ein Iterator. Danke an wob für die Erleuchtung.
template<class T> T* Catalog<T>::find(ulong id) const { auto it = map.find(id); return it != map.end() ? it->second.get() : nullptr; }
Ich würde mich dennoch über Antworten, meine weiteren Überlegungen betreffend, freuen (siehe oben).
Speziell dieses würde mich recht reizen.
auto l = catalog.find(42).asLocation();
-
temi schrieb:
Hab ich grad ausprobiert, das kompiliert nicht.
Ok, da habe ich zu viel vereinfacht. Es braucht schon einen Klassentypen. Probiere doch statt int mal
struct Foo { int i };
aus und setzte new Foo statt new int ein.
-
wob schrieb:
temi schrieb:
Hab ich grad ausprobiert, das kompiliert nicht.
Ok, da habe ich zu viel vereinfacht. Es braucht schon einen Klassentypen. Probiere doch statt int mal
struct Foo { int i };
aus und setzte new Foo statt new int ein.Fehler: cannot dynamic_cast 'object' (of type 'struct Foo*') to type 'class tad::Object*' (source type is not polymorphic)
map.insert(std::make_pair<ulong, std::unique_ptr<T>>(dynamic_cast<Object*>(object)->getId(), std::unique_ptr<T>(object)));
-
temi schrieb:
Fehler: cannot dynamic_cast 'object' (of type 'struct Foo*') to type 'class tad::Object*' (source type is not polymorphic)
map.insert(std::make_pair<ulong, std::unique_ptr<T>>(dynamic_cast<Object*>(object)->getId(), std::unique_ptr<T>(object)));Dann spendiere dem Foo halt noch eine virtuelle Funktion!
-
wob schrieb:
temi schrieb:
Fehler: cannot dynamic_cast 'object' (of type 'struct Foo*') to type 'class tad::Object*' (source type is not polymorphic)
map.insert(std::make_pair<ulong, std::unique_ptr<T>>(dynamic_cast<Object*>(object)->getId(), std::unique_ptr<T>(object)));Dann spendiere dem Foo halt noch eine virtuelle Funktion!
Ich gebe auf.
Tad3 ist abgestürzt
static_assert(std::is_base_of<Object, T>::value, "Catalog<T>: T is not derived from Object");
Hat geklappt.
-
Das ist jetzt eher eine theoretische Spielerei, aber es würde mich dennoch interessieren. Ich schreibe die Frage in dieses Thema, weil sie eine Ergänzung zur vorherigen Diskussion ist.
// --- diese Lösung funktioniert Catalog catalog{new Converter}; // Der Catalog bekommt einen Converter für die Umwandlung der Rückgabewerte mit catalog.add(new Item{"Foo"}); // neues Item : public Object => Catalog kann nur Object* speichern! catalog.add(new Location{"Bar"}); // neue Location : public Object catalog.find(1).asItem(); // .find liefert den Converter zurück und dieser enthält die Funktion .asItem() catalog.find(2).asLocation(); // dito nur diesmal ist es die Funktion .asLocation() // werden neue Typen benötigt, dann muss nur der Converter angepasst werden // --- Ist das irgendwie umsetzbar??? Catalog catalog{}; catalob.AddConverter{new ItemConverter}; // dieser Katalog kann nur "Items" zurückgeben catalog.addObject(new Item{"Foo"}); catalog.find(1).asItem(); catalog.addConverter{new LocationConverter}; // jetzt kann er "Items" und "Locations" zurückgeben catalog.addObject(new Location{"Bar"}); catalog.find(1).asLocation(); // nullptr muss vom Aufrufer behandelt werden! catalog.find(2).asLocation(); // zur Erweiterung des Katalogs müsste ein zusätzlicher Konverter hinzugefügt werden
Ich habe mich bereits nach möglichen Lösungen umgeschaut, aber bisher noch nichts Brauchbares gefunden. Die Konverter müssten in einer Liste gespeichert werden, durch die iteriert und der passende Konverter aufgerufen wird. Knackpunkt ist der Rückgabewert von .find(), denn der würde dann dem jeweils gefundenen Konverter entsprechen müssen.
Gruß,
temi
-
Ich habe jetzt "meine" Lösung gefunden und glaube dass sie ganz gut gelungen ist.
class Catalog { public: Catalog() {}; void add(Object* object); template<class T> bool exists(ulong id); template<class T> T* get(ulong id); private: std::unordered_map<ulong, std::unique_ptr<Object>> map; }; template<class T> bool Catalog::exists(ulong id) { static_assert(std::is_base_of<Object, T>::value, "Catalog.exists<T>(id): T is not derived from Object"); return get<T>(id) != nullptr; } template<class T> T* Catalog::get(ulong id) { static_assert(std::is_base_of<Object, T>::value, "Catalog.get<T>(id): T is not derived from Object"); return dynamic_cast<T*>(map[id].get()); }
Damit kann ich beliebige von "Object" abgeleitete Objekte speichern und erhalte den korrekten Typen über ein klares Interface wieder zurück.
int main(int argc, char *argv[]) { Catalog catalog; catalog.add(new Item{"Axt"}); catalog.add(new Item{"Baum"}); catalog.add(new Location{"Wald"}); if (catalog.exists<Item>(42)) cout << catalog.get<Item>(42)->getName() << endl; else cout << "Can't fing matching item" << endl; cout << catalog.exists<Item>(1) << endl; cout << catalog.exists<Item>(2) << endl; cout << catalog.exists<Item>(3) << endl; cout << catalog.exists<Location>(1) << endl; cout << catalog.exists<Location>(2) << endl; cout << catalog.exists<Location>(3) << endl; cout << catalog.get<Item>(1)->getName() << endl; cout << catalog.get<Item>(2)->getName() << endl; cout << catalog.get<Location>(3)->getName() << endl; }
Über Lob oder Kritik würde ich mich weiterhin freuen. Es lässt sich einfacher lernen, wenn man auf Fehler hingewiesen wird und diese dann in Zukunft vermeiden kann. Außerdem wäre ich auch weiterhin an Antworten auf die eine oder andere Frage aus den vorherigen Beiträgen interessiert, obwohl ich das meiste davon bereits wieder verworfen habe.
Danke und Gruß,
temi