Datalayer - Template Typ speichern?
-
Hi!
Eine Möglichkeit der Query-Typen beim Aufruf von
get()
zu vermeiden, ist diesen in der Objektklasse zu hinterlegen:class Scene { ... using QueryType = SceneQuery; }; class Item { ... using ItemType = ItemQuery; };
Oder wenn du das vermeiden möchtest, kann man die Verknüpfung von Objektklasse und Queryklasse in eine separaten "Traits"-Klasse packen.
Dazu hatte ich eben, bevor ich deine letzte Antwort gelesen hatte ein kurzes Beispiel erstellt:... template <typename T> struct QueryTraits { }; class Catalog { public: template<class T> void registerQuery(typename QueryTraits<T>::QueryType query) { queries[typeid(T).name()] = std::make_unique<QueryTraits<T>::QueryType>(query); } template<class T> std::unique_ptr<T> get() { std::unique_ptr<T> result; // Verwende Iterator, um doppelte Suche in unordered_map zu vermeiden // (einmal via find() und einmal via []-Operator, das war etwas ineffizient). auto iter = queries.find(typeid(T).name()); if(iter != queries.end()) { // Dynamischer Cast nach QueryType für den angegebenen Typen. auto query = dynamic_cast<typename QueryTraits<T>::QueryType*>(iter->second.get()); // Wenn in Map gespeicherte Query den korrekten Typen hat, dann Pointer zurückgeben. if (query != nullptr) result = query->get(); } // ... ansonsten nullptr zurückgeben (Default-Wert von unique_ptr) return result; } private: // Man muss in der Map mit Pointern arbeiten, da man ansonsten ein Problem bekommt, // das als "Object Slicing" bekannt ist: Nur der IQuery-Anteil der Objekte wird gepeichert, // und sie verlieren quasi ihren "Dynamischen Typen" (der abgeleitete Query Typ). std::unordered_map<std::string, std::unique_ptr<IQuery>> queries; }; ... template <> struct QueryTraits<Scene> { using QueryType = SceneQuery; }; template <> struct QueryTraits<Item> { using QueryType = ItemQuery; }; ... int main(int argc, char *argv[]) { // Wunschlösung Catalog catalog; catalog.registerQuery<Scene>(sceneQuery); auto scene2 = catalog.get<Scene>(); scene2->say(); }
Wie gesagt, das "
using QueryType = SceneQuery;
" kann man auch in die Scene/Item-Klasse packen. Manchmal möchte man jedoch solche Dinge trennen.Hoffe das ist Inspiration genug, um dein Problem, zu lösen.
Gruss,
Finnegan
-
hier meine loesung.
die klasse IQueryID::id macht im grunde das selbe wie std::locale::id.
ich verwende sie hier um den IQuery* klassen eindeutige IDs automatisch zu
vergeben. dadurch kann die catalog klasse zum registrieren einen vector
verwenden und der zugriff auf das gewuenscht object kann mit dem operator[] realisiert werden.class IQueryID { public: class id { public: using value_type = unsigned int; id(value_type val = 0) : m_id(val) { } id(const id&) = delete; auto operator=(const id&) -> id& = delete; operator value_type() { if(m_id == 0) { m_id = ++s_id; } return m_id; } private: value_type m_id; static value_type s_id; }; }; unsigned int IQueryID::id::s_id = 0; class IQuery { public: virtual auto show_message(const std::string &str) -> void = 0; virtual auto get_id(void) noexcept -> IQueryID::id::value_type = 0; }; class catalog { public: auto register_query(IQuery *q) -> void { auto t_id = q->get_id(); if(t_id > m_queries.size()) { m_queries.resize(t_id, nullptr); } m_queries[t_id - 1] = q; } template<class T> auto get(void) -> T* { return reinterpret_cast<T*>(m_queries[T::unique_id - 1]); } private: std::vector<IQuery*> m_queries; }; class IQuery1 : public IQuery { public: static IQueryID::id unique_id; virtual auto show_message(const std::string &str) -> void { MessageBoxA(0, str.c_str(), "IQuery1", MB_OK); } virtual auto get_id(void) noexcept -> IQueryID::id::value_type { return static_cast<unsigned int>(unique_id); } }; IQueryID::id IQuery1::unique_id; class IQuery2 : public IQuery { public: static IQueryID::id unique_id; virtual auto show_message(const std::string &str) -> void { MessageBoxA(0, str.c_str(), "IQuery2", MB_OK); } virtual auto get_id(void) noexcept -> IQueryID::id::value_type { return unique_id; } }; IQueryID::id IQuery2::unique_id; class IQuery3 : public IQuery { public: static IQueryID::id unique_id; virtual auto show_message(const std::string &str) -> void { MessageBoxA(0, str.c_str(), "IQuery3", MB_OK); } virtual auto get_id(void) noexcept -> IQueryID::id::value_type { return unique_id; } }; IQueryID::id IQuery3::unique_id; auto WINAPI WinMain(HINSTANCE, HINSTANCE, char* , int) -> int { catalog cat; cat.register_query(new IQuery1); cat.register_query(new IQuery2); cat.register_query(new IQuery3); cat.get<IQuery1>()->show_message("das ist ein test"); cat.get<IQuery2>()->show_message("das ist ein test"); cat.get<IQuery3>()->show_message("das ist ein test"); return 0; }
-
Vielen Dank an Finnegan für das ausführliche Beispiel mit den Traits. Das war neu für mich.
Vielen Dank auch an Meep Meep. Ich muss dein Beispiel erst mal durcharbeiten, um zu verstehen, was du da machst.
Ziel der ganzen Spielerei ist ja die Entkoppelung, insofern möchte ich nicht innerhalb der Objektklassen Informationen zu konkreten Implementationen der zugehörigen Queries hinterlegen. Muss ja auch nicht sein. Die Trait-Spezialisierung kommt einfach in den Header der jeweiligen Query (denn der Katalog soll ja auch nichts über konkrete Implementierungen wissen. Ich habe das auch gleich mal umgesetzt.
// catalog.h template <typename T> struct QueryTraits {}; class Catalog { public: template<class T> void registerQuery(std::unique_ptr<IQuery> query) { queries[typeid(T).name()] = std::move(query); } template<class T> std::unique_ptr<T> get(const std::string& id) { auto found = queries.find(typeid(T).name()); if(found != queries.end()) { if (auto query = dynamic_cast<typename QueryTraits<T>::QueryType*>(found->second.get())) { return query->get(id); } } return nullptr; } private: std::unordered_map<std::string, std::unique_ptr<IQuery>> queries; }; // z.B. itemquery.h class ItemQuery : public AbstractQuery<Item> { public: std::unique_ptr<Item> get(const std::string& id) override { return std::unique_ptr<Item>(new Item{}); } }; template <> struct QueryTraits<Item> { using QueryType = ItemQuery; };
Beim Ausprobieren bin ich jetzt aber etwas überrascht worden
int main(int argc, char *argv[]) { Catalog catalog; catalog.registerQuery<Scene>(std::unique_ptr<SceneQuery>(new SceneQuery)); // irgendwelcher Code catalog.get<Scene>("dummy")->say(); catalog.get<Item>("dummy")->say(); // WTF!!! } // Ausgabe: // Scene! // Item!
Obwohl keine ItemQuery registriert ist, erhalte ich trotzdem eine Ausgabe auf der Konsole und das Programm beendet ohne Fehler. Funktioniert übrigens auch ohne eine SceneQuery zu registrieren. Oder sogar so:
class Catalog { public: template<class T> void registerQuery(std::unique_ptr<IQuery> query) { queries[typeid(T).name()] = std::move(query); } template<class T> std::unique_ptr<T> get(const std::string& id) { // auto found = queries.find(typeid(T).name()); // if(found != queries.end()) // { // if (auto query = dynamic_cast<typename QueryTraits<T>::QueryType*>(found->second.get())) // { // return query->get(id); // } // } std::cout << "WTF!" << std::endl; return nullptr; } private: std::unordered_map<std::string, std::unique_ptr<IQuery>> queries; }; // Ausgabe: // WTF! // Scene! // WTF! // Item!
-
Offenbar funktioniert der Code soweit, denn wenn ich den Rückgabewert korrekt mit Prüfung auf nullptr weiter verarbeite, dann verhält sich alles wie erwartet.
auto s1 = catalog.get<Scene>("dummy"); auto i1 = catalog.get<Item>("dummy"); if (s1) s1->say(); if (i1) i1->say();
Aber ich hätte bei Zugriff auf einen Nullptr irgendeine Fehlermeldung erwartet, anstatt tatsächlich die korrekte Ausgabe zu liefern. Wie kommt's?
-
Meep Meep schrieb:
hier meine loesung.
class IQueryID operator value_type() // was bewirkt das? { if(m_id == 0) { m_id = ++s_id; } return m_id; } // War mir neu, dass sowas geht. Hat das einen Vorteil? auto register_query(IQuery *q) -> void }
Falls ich das alles einigermaßen verstanden habe, dann löst es nicht mein Problem, denn ich möchte ja mit get() nicht die Query als Rückgabewert sondern das Ergebnis der Query.
Ich habe oben dennoch noch ein paar Fragen reingeschrieben, weil mir hier einige neue unbekannte Dinge begegnet sind. Insofern noch einmal Danke dafür.
Danke und Gruß.
temi
-
operator value_type() ist ein konvertierungsoperator.
er castet das id_object nach value_type, wobei value_type ein alias fuer unsigned int ist
-
temi schrieb:
Falls ich das alles einigermaßen verstanden habe, dann löst es nicht mein Problem, denn ich möchte ja mit get() nicht die Query als Rückgabewert sondern das Ergebnis der Query.
temiich versteh wohl nicht ganz wie du das meinst.
temi schrieb:
std::unique_ptr<T> get(const std::string& id) { auto found = queries.find(typeid(T).name()); if(found != queries.end()) { if (auto query = dynamic_cast<typename QueryTraits<T>::QueryType*>(found->second.get())) { return query->get(id); } } return nullptr; }
wenn ich das richtig sehe, suchst du in get nach dem konkreten IQuery und rufst von dem IQuery-Objekt die get-methode auf in dem du als parameters den std::string(id) übergibst. die methode Catalog::get gibt das das ergebnis zurueck.
bei meiner variante lass ich mir das objekt zurueck geben und ruf dann selber die get-methode von dem IQuery-Objekt auf. da wurde nur der aufruf nach aussen verlagert. du kannst das natuerlich auch in der Catalog::get methode machen.
ich hab es extern gemacht weil ich den ruckgabewert nicht wusste und auch nicht weiß ob jedes IQuery Objekt immer den selben Typ zurueckgibt.
-
[quote="Meep Meep"]
temi schrieb:
temi schrieb:
std::unique_ptr<T> get(const std::string& id) { auto found = queries.find(typeid(T).name()); if(found != queries.end()) { if (auto query = dynamic_cast<typename QueryTraits<T>::QueryType*>(found->second.get())) { return query->get(id); } } return nullptr; }
wenn ich das richtig sehe, suchst du in get nach dem konkreten IQuery und rufst von dem IQuery-Objekt die get-methode auf in dem du als parameters den std::string(id) übergibst. die methode Catalog::get gibt das das ergebnis zurueck.
bei meiner variante lass ich mir das objekt zurueck geben und ruf dann selber die get-methode von dem IQuery-Objekt auf. da wurde nur der aufruf nach aussen verlagert. du kannst das natuerlich auch in der Catalog::get methode machen.
ich hab es extern gemacht weil ich den ruckgabewert nicht wusste und auch nicht weiß ob jedes IQuery Objekt immer den selben Typ zurueckgibt.Richtig! Die get-methode der Query sucht (irgendwo: Datei, DB, Internet) über eine id nach einem bestimmten Object. Eine ItemQuery sucht nach einem Item und liefert ein Item zurück. Eine SceneQuery sucht nach einer Scene und liefert diese zurück. Der Katalog ist nur ein Sammler für die einzelnen Queries. Leider kann allerdings auf dem IQuery kein get() aufgerufen werden, weil get() ja unterschiedliche Rückgabewerte liefert und ein Template nicht in der Map gespeichert werden kann. Deshalb wird IQuery auf die konkrete Query gecastet, deren get-Methode aufgerufen und der Rückgabewert T geliefert.
Möglich wäre jetzt get<T, Q>() (Typ der Query), aber die Benutzer des Katalogs sollen die konkrete Query nicht kennen müssen.Ziel ist Entkoppelung der Daten(beschaffung) von der Logik und die einfache Erweiterbarkeit (neue Datenklasse und neue Query in den Katalog).
Dein Beispiel war auch sehr lehrreich für mich. Falls mich noch jemand kurz über das oben aufgetretene Problem erhellen könnte, dann wäre das sehr nett.
Gruß,
temi
-
temi schrieb:
Offenbar funktioniert der Code soweit, denn wenn ich den Rückgabewert korrekt mit Prüfung auf nullptr weiter verarbeite, dann verhält sich alles wie erwartet.
auto s1 = catalog.get<Scene>("dummy"); auto i1 = catalog.get<Item>("dummy"); if (s1) s1->say(); if (i1) i1->say();
Aber ich hätte bei Zugriff auf einen Nullptr irgendeine Fehlermeldung erwartet, anstatt tatsächlich die korrekte Ausgabe zu liefern. Wie kommt's?
du kannst memberfunktionen einer klasse problemlos aufrufen, auch wenn der zeiger auf 0 zeigt. du darfst in der methode nur nicht auf membervariablen zugreifen, dann schepperts.
-
Meep Meep schrieb:
du kannst memberfunktionen einer klasse problemlos aufrufen, auch wenn der zeiger auf 0 zeigt. du darfst in der methode nur nicht auf membervariablen zugreifen, dann schepperts.
Ah, gut zu wissen. Danke!