Deserialisierung - doppelten Initialisierungscode vermeiden
-
Hallo zusammen,
ich habe eine Klasse, deren Instanzen übers netzwerk verschickt werden können. Die Methode Deserialize sorgt dann fürs aktualisieren der Werte basierend auf den Netzwerkdaten.
Außerdem soll es möglich sein, ein Objekt direkt aus den Netzwerkdaten zu erstellen.
Ein großer Teil des Initialisierungscode ist allerdings unabhängig von den Netzwerkdaten (nur ein relativ kleiner teil der Daten wird übers Netzwerk synchronisiert).
Um doppelten Code zu vermeiden sieht das im Moment ungefähr so aus:class GameObject { public: GameObject() { Init(); } GameObject(Packet& packet) { Init(); //erst standard initialisieren Deserialize(packet, true); //und dann werte basierend auf netzwerkdaten anpassen //das deserialize wird in der basisklasse nicht aufgerufen, es steht hier nur um zu zeigen, wie es in einer abgeleitetn klasse aussieht } //initializing gibt an, ob das objekt gerade initialisiert wird, oder ob es nur durch die netzwerkdaten aktualisiert wird virtual void Deserialize(Packet& packet, bool initializing) { //daten aus dem netzwerk anwenden } private: void Init(); };
Init kann auch Parameter haben, die habe ich nur erstmal weggelassen, weil sie hier nicth wichtig sind.
Ich will damit eine Vererbungshierarchie aufbauen und alle abgeleiteten Klassen werden dann nach dem selben Schema aufgebaut (mindestens 2 konstruktoren, wobei einer ein Paket übernimmt und Deserialize aufruft).
Allerdings darf Deserialize nur vom Konstruktor der am meisten abgeleiteten Klasse aufgerufen werden, weil sonst der vptr und der restliche state nicht richtig initialisiert ist oder die Methode mehrfach aufgerufen wird.
Das Problem ist: Wie kann ich sicherstellen, dass Deserialize in der am meisten abgeleiteten Klasse in dem entsprechenden Konstruktor aufgerufen wird (und nur da)?Und falls ich eine konkrete Klasse haben will, muss diese Deserialize im Konstruktor aufrufen. Falls ich von dieser konkreten Klasse ableiten will, habe ich ein Problem, weil dann Deserialize schon in der Basisklasse aufgerufen wird.
Ich weiß, dass es meistens keine gute Idee ist, von konkreten Klassen abzuleiten, bisher bin ich dabei ohne ausgkeommen, aber ein Problem könnte es vielleicht irgendwann werden.Wisst ihr dafür eine Lösung?
Oder gibt es evtl. grundsätzlich bessere Vorgehensweisen als das was ich bisher gemacht habe?Vielen Dank schonmal für eure Hilfe!
-
gelöscht, war nicht passend.
-
tststst ... virtuelle Methoden im Konstruktor Wie waere es mit einer Factory?
Ich will
Genau das ist das Problem. Dein Plan ist schlecht. Die Maengeln kennst du bereits. Warum muss deserialize im Konstruktor aufgerufen werden?
Wie kann ich sicherstellen, dass Deserialize in der am meisten abgeleiteten Klasse in dem entsprechenden Konstruktor aufgerufen wird (und nur da)?
Fang erst garnicht damit an. Ausserdem wuerde das nichts aendern.
-
Der sinn davon deserialize im konstruktor aufzurufen ist ein objekt direkt vom netzwerk aus zu erstellen.
Und ich wollte vermeiden, dass ich mehrere versionen von deserialize habe, deswegen benutze ich die selbe methode zum updaten und im konstruktor.Wie würdest du es denn machen? Von außen erst irgendwie initialisieren und dann deserialize aufrufen? Das ist eigentlich nicht wirklich möglich, weil jedes GameObject beim erstellen eine eindeutige ID zugewiesen bekommt und die id mit übers netzwerk gesendet wird. Wenn dann müsste ich zuerst mit einer ungültigen Id initialisieren und diese dann überschreiben. Wirklich toll finde ich das aber auch nicht.
Wie stellst du dir das mit der factory vor? Soll die factory dann ein objekt erst defaultkonstruieren und dann deserialize aufrufen?
-
Was, wenn deine Netzwerkdaten fehlerhaft sind? Exceptions? Ja, die Factory erstellt ein Objekt. Ja, die Factory erstellt alle Objekte, d.h. sie entscheidet auch ueber die ID. Ja sie ruft irgendwie deserialize auf. Deserialize muss aber keine Methode von GameObject sein.
-
Wenn Deserialize keine methode von gameobject ist, dann muss ich friend oder eine ganze menge von accessor methoden verwenden, ich wüsste nicht, wie ich das sonst implementieren sollte. Ich sehe auch keinen Vorteil darrin, die Deserialize-Methode auszulagern.
Das mit der Factory ist im prinzip machbar, da ist nur das problem mit der id, aber dafür könnte ich eine lösung finden.
Ich benutze eine Netzwerkbibliothek, die dafür sorgt, dass die Daten fehlerfrei übertragen werden. Bisher hatte ich nie Probleme mit fehlerhaften Daten und ich wüsste nicht, warum ich damit jemals probleme kriegen sollte. Falls die Daten wirklich fehlerhaft sein sollten, fliegt höchstwahrscheinlich eine exception.
-
Virtueller Methodenaufruf im Konstruktor ruft per Definition nicht die Methode der "am meisten abgeleiteten Klasse" auf. Selbige ist zu dem Zeitpunkt da du im Konstruktor der Basis bist noch gar nicht konstruiert.
Q schrieb:
Wie stellst du dir das mit der factory vor? Soll die factory dann ein objekt erst defaultkonstruieren und dann deserialize aufrufen?
Die Factory kann einfach einen entsprechenden konkreten Konstruktor aufrufen, der dann eben Deserialize macht. Oder eine konkrete Deserialize Funktion, die eben ein neues Objekt erzeugt, oder sonstwas. Der Punkt ist: Die Factory kennt den konkreten Typ und daher gibts keinen Grund für den virtuellen Methodenaufruf im Konstruktor der Basis, der eben aus Prinzip nicht das von dir erwartete Ergebnis liefern kann.
-
dot schrieb:
virtuelle Methodenaufruf im Konstruktor ruft per Definition nicht die Methode der "am meisten abgeleiteten Klasse" auf. Selbige ist zu dem Zeitpunkt da du im Konstruktor der Basisklasse bist noch gar nicht fertig initialisiert.
Ich rufe das deserialize ja nicht aus der basis auf, das war oben beim codebeispiel vielleicht etwas irreführend. Ich wollte da nur die Struktur zeigen.
Das Deserialize wird im Moment immer nur aus dem konstruktor der am meisten abgeleiteten klasse aufgerufen, was dann auch funktioniert, nur nicht besonders flexibel ist (wenn ich z.B. davon wieder ableite funktioniert es nicht mehr).
Die methode ist eigentlich nur virtuell, damit jede ableitende klasse etwas zum vorgang des deserialisierens hinzufügen kann. Alternativ könnte man die methoden der basisklasse auch verdecken, aber ich denke nicht das das viel besser ist.
-
Der Konstruktor ruft keine in der konkreten Klasse implementierten virtuellen Methoden auf. Alles andere ist Glueck, bzw. nicht definiertes Verhalten.
-
Q schrieb:
Das Deserialize wird im Moment immer nur aus dem konstruktor der am meisten abgeleiteten klasse aufgerufen, was dann auch funktioniert, nur nicht besonders flexibel ist (wenn ich z.B. davon wieder ableite funktioniert es nicht mehr).
Ist es so, daß nur die am meisten abgeleiteten Klassen konkret sind und alle anderen Klassen, also die, von denen geerbt wird, abstrakt sind?
Sollte es so sein?
-
Bisher ist es so, dass alle außer den am meisten abgeleiteten klassen abstrakt sind und ich habe noch nicht geplant das zu ändern, aber ich könnte mir schon vorstellen, dass irgendwann mal ein fall auftritt in dem ich von einer konkreten klasse erben will.
knivil schrieb:
Der Konstruktor ruft keine in der konkreten Klasse implementierten virtuellen Methoden auf. Alles andere ist Glueck, bzw. nicht definiertes Verhalten.
Wenn ich folgendes hab:
class Base { public: virtual void A(){} }; class Derived : public Base { public: Derived(){A();} virtual void A() override {} };
Dann ist das verhalten vom Derived konstruktor doch wohl definiert.
Ich rufe virtuelle methoden nur in den am weitesten abgeleiteten Klassen auf.
-
Welche virtuelle Methode A() soll denn im Konstruktor von Derived aufgerufen werden, die von Base oder die von Derived? Wenn es Derived::A() sein soll, muss ich dich enttaeuschen, du hast reines Glueck.
Ansonsten gibt es ziemlich weit am Ende auch etwas zu virtuellen Funktionen: http://www.slideshare.net/olvemaudal/deep-c
-
Es sollte Derived::A() sein. Wundert mich jetzt sehr, dass das nicht definiert ist.
Aber vielen Dank für eure Hilfe, ich werde das dann mit der Factory einbauen, das hat auch noch einen anderen positiven nebeneffekt.Vorher hatte ich beim erstellen vom netzwerk einen großen switch-case block, ich werde das dann vermutlich durch die Fabrik ersetzen und in der fabrik dann eine map benutzen, die enum-werte auf funktionen mappt, die einen konstruktor mit new aufrufen.
Jede konrete Game-Objekt-Klasse registriert sich dann bei der Factory.Ich habe vor, die Factory dann statisch und global zu machen (oder singleton), damit sich jeder dort registrieren kann. Nur gibt es dann evtl. Probleme mit der initialisierungsreiehenfolge...
Habt ihr dazu noch tipps?