Dicken Constructor auslagern + Vererbungsmöglichkeit erhalten?
-
PIMPL wird demnaechst angeschaut!

Model und ViewedModel koennen beide benutzt werden. Model ist halt ausserhalb des Kamerakontextes zu benutzen, den meine Engine anbieten wird, ViewedModel steht darin. Beide haben daher ihre Berechtigung, technisch ist ViewedModel aber eine Erweiterung in dem Sinne, dass es mit anderen Objekten interagiert und Positions/Rotations/Skalierungsdaten enthaelt und dafuer auch einige Methoden enthaelt.
D.h. der Teil, welcher die eigentlichen Modelldaten laedt, ist gleich. Polymorphie ist hier eigentlich nie notwendig.
-
Hat das Objekt einen "natürlichen, leeren" Zustand, dann biete einen Default-Ctor an, der ein leeres Objekt erstellt.
Hat das Objekt keinen solchen Zustand, dann biete eben keinen Default-Ctor an.Kann das Objekt von aussen (
public) bearbeitet werden, dann kannst du die "Loader" komplett unabhängig davon implementieren. Der Loader legt sich dann einfach ein neues (leeres) Objekt an, und steckt über diepublicSchnittstelle die Daten rein.Kann das Objekt nicht von aussen bearbeitet werden (oder nicht vollständig), dann gibt es verschiedene Möglichkeiten. Die wohl einfachste, aber nicht immer beste, ist, dem Objekt selbst das Laden aufzubürden.
Besser kann es gehen, wenn man trotzden "Loader" Klassen implementiert. Diese können dann nicht mehr über die
publicSchnittstelle mit dem Objekt kommunizieren, also werden siefriend.Eine andere Möglichkeit wäre, dass die Loader-Klassen nur eine "Bauanleitung" (Zwischenformat) für das Objekt erstellen, und mit dieser dann das neue Objekt erstellen. Wie gut das geht/sinnvoll das ist, hängt von der Art des Objekts ab. Manchmal ist die "Bauanleitung" genau so kompliziert wie das eigentliche Objekt, bzw. würde sogar 100% gleich aussehen. Dann macht die Unterscheidung keinen Sein. Manchmal geht es allerdings wieder recht gut. Beispiel: Texturen für ein 3D Spiel. Wenn wir HDR und komprimierte Texturformate mal ignorieren, dann könnte man aus "Bauanleitung" einfach ein Array aus 32 Bit Pixeln im RGBA Format verwenden. Der Loader würde also das entpacken von PGN, JPEG, ... übernehmen, und die Textur-Klasse das Konvertieren in das Farbformat der Grafikkarte und den Upload in den Texturspeicher.
-
Zu ein paar anderen Dingen...
PIMPL hat mit der hier diskutierten Frage IMO nichts zu tun.
Dass ein Ctor nur wenig tun sollte halte ich auch für eine sehr fragwürdige Richtlinie.
Selbst dass der Default-Ctor nur wenig tun sollte halte ich für fragwürdig. Ich würde sagen der Default-Ctor sollte nichts tun, was nicht nötig ist, bzw. wofür es nicht einen guten Grund gibt, wenn es "teuer" ist. Beispiel für was ich meine: der Default-Ctor von
std::vectorsollte keinreservemachen.Andrerseits sollte ein Default-Ctor IMO alles nötige machen, um unnötige "halb initialisierte" Zustände zu vermeiden. Beispiel: eine Socket-Klasse sollte sich IMO im Default-Ctor ein Socket-Handle holen, um den "not-a-socket"/"null"/"empty" Zustand zu vermeiden, auch trotz dem das Holen eines neuen Socket Handles vermutlich ein paar (zig?) Tausend Zyklen "teuer" ist.
-
Zum Pimpl: Der primäre Zweck des Handle-Body-Idioms besteht darin, Compilezeit-Abhängigkeiten zu verringern. Durch die Auslagerung der Implementierung kann diese (d.h. auch Memberdeklarationen der Impl-Klasse) geändert werden, ohne Client-Code neu zu kompilieren. Zudem müssen die Headerdateien für Membervariablen nicht im Client-Code mitkompiliert werden.
Eine Trennung im Sinne des Pimpl-Idioms teilt die Aufgabenbereiche der Klasse nicht wirklich auf mehrere Klassen auf, sondern lagert sie nur aus.
-
Okay. Cool, danke, ich geh Mal der Reihe nach durch

hustbaer:
Das Wort natürlich scheint beim leeren ctor die Hauptrolle zu spielen, okay.Für mich besteht dann weiterhin hauptsächlich die Frage, was die Vorteile davon sind, den Loader auszulagern, wenn man nur ein erzeugtes Objekt (im Ggs. zu dem üblichen Anwendungsfall von Factoryfunktionen/klassen) hat. Mir fällt es schwer, da die Vorteile zu sehen, wenn man von schlankerem Header absieht, da es für einige Aspekte (z.B. Vererbung) ja doch praktisch ist, wenn das Objekt sich selbst erstellt.
Ich bin ja auf der Suche nach der besten Lösung, aber nicht sicher, wie ich diese finden kann.
Insgesamt habe ich den ctor jetzt glaube ich etwas besser verstanden. Ich glaube, ich muss einfach mehr in Objekten denken, keine Ahnung.
Und wenn ich mehrere Erzeuger habe? Die Abstract Factory läuft im klassischen Klassendesign ja auch nicht so gut mit Vererbung, oder? Was haltet ihr denn dann von der Implementierung:
class ModelDataLoader { public: void LoadModelData(Model* model, const string& str) = 0; }; class ModelDataLoader1 : public ModelDataLoader { public: void LoadModelData(Model * model, const string& str); }; class ModelDataLoader2 : public ModelDataLoader { public: void LoadModelData(Model* model, const string& str); }; class Model { private: // attributes // mit friend zu ModelDataLoader public: Model() {} }; class ViewedModel : public Model { private: // attributes und zusätzliche attribute public: ViewedModel() {} // zusätzliche setter für Attribute, die nur ViewedModel hat? (X) }; int main() { typedef boost::scoped_ptr<Model*> ModelPtr; typedef boost::scoped_ptr<ViewedModel*> ViewedModelPtr; ModelDataLoader l1; ModelDataLoader l2; ModelPtr model1(new Model); ModelViewedPtr model2(new ViewedModel); l1.LoadModelData(model1); l2.LoadModelData(model2); return 0; }Also Loader1/2 hat jetzt nix mit Model/ViewedModel zu tun. Nachteil dieser Methode hier ist, dass ein Model eigentlich im leeren Zustand eigentlich sinnfrei ist. Vorteil ist aber, dass ich vererben kann wie blöd.
- Was haltet ihr davon?
- Eigentlich ist ViewedModel ein Model, was betrachtet werden kann. Man könnte aber auch formulieren, ein ModelView ist ein Model, das mit Positionsdaten versehen ist, d.h. ich könnte das auch über Komposition statt Vererbung lösen. Was haltet ihr von so was?
Danke so weit!
-
- Eigentlich ist ViewedModel ein Model, was betrachtet werden kann. Man könnte aber auch formulieren, ein ModelView ist ein Model, das mit Positionsdaten versehen ist, d.h. ich könnte das auch über Komposition statt Vererbung lösen. Was haltet ihr von so was?
Ich würde weder das eine noch das andere machen.
Eher eine "ModelInstance" Klasse:
class ModelInstance { public: ModelInstance(shared_ptr<Model> const& model, Matrix4x4 const& position /* ... */); private: shared_ptr<Model> m_model; Matrix4x4 m_position; // evtl. weitere Daten für Vertex-Blending, ... };Also ein Verweis auf ein Model + Position wo es zu sehen sein soll.
-
Scheiße, ja! Das macht ja sowieso Sinn, weil die Anzeige eines Models zum Model selbst ja 1 : n ist. Das muss ich mir nochmal durch den Kopf gehen lassen, danke.

Und zu folgender Frage:
Für mich besteht dann weiterhin hauptsächlich die Frage, was die Vorteile davon sind, den Loader auszulagern, wenn man nur ein erzeugtes Objekt (im Ggs. zu dem üblichen Anwendungsfall von Factoryfunktionen/klassen) hat.
?
-
Eisflamme schrieb:
Für mich besteht dann weiterhin hauptsächlich die Frage, was die Vorteile davon sind, den Loader auszulagern, wenn man nur ein erzeugtes Objekt (im Ggs. zu dem üblichen Anwendungsfall von Factoryfunktionen/klassen) hat.
?
Naja... pfuh.
Kommt jetzt drauf an.
Wenn das Datenformat wie es im File steht quasi nur eine 1:1 Serialisierung von dem ist wie die Daten im Speicher liegen, dann ist der Sinn erstmal fraglich.
Was man immer heraustrennen sollte sind Dinge wie der "Formattierer" (bei Boost.Serialization übernehmen den Job IIRC die Archiv-Klassen), damit man z.B. wahlweise XML oder Binärdateien speichern/laden kann.Wenn das Datenformat im File nicht 1:1 dem enstpricht wie's im Speicher liegt, dann gehört das IMO in eine eigene Klasse. Die Model Klasse ist das Model, und kümmert sich um Model-Angelegenheiten. Dem Model kann es egal sein wie das .obj, .x, .fbx etc. File-Format aussieht.
Die Aufgabe des Loaders/Parsert/... ist es dann jeweils "sein" File-Format zu kennen. D.h. du hast einen XFileLoader, einen ObjFileLoder, einen FbxFileLoader etc.
Und selbst wenn du nur ein Dateiformat unterstützen willst/musst: das "dekodieren" eines bestimmten Formats hat IMO in der Daten-Klasse nichts verloren.
Denk einfach an das Beispiel Textur- oder Image-Klasse. Die Textur- oder Image-Klasse sollte sich IMO nicht darum kümmern müssen wie ein PNG, JPEG, BMP etc. zu lesen geht. Das macht je eine eigene Klasse pro File-Format.
-
Okay, das macht Sinn, vielen Dank.

Aber das wird bei mir jetzt wohl doch darauf hinauslaufen, dass ich allen meinen Loaderklassen einfach friend-Zugriff auf Model erlaube. Dafür muss Model natürlich auch die Header der Loaderklassen kennen. Damit kennt Model seine Erzeuger, ist das unerwünscht?
-
Vielleicht kannst du ja
friendauf eine kleine interne (nicht zum API gehörende) Zwischenklasse beschränken, um so eine Abstraktionsschicht zu haben.
-
Also da die Fremd-API ja nicht direkt in mein Format konvertiert, habe ich eh eine Vermittlerklasse, die halt trotzdem einer der möglichen ModelLoader ist. Zumindest wäre das Einführen von einer Zwischenklasse wohl nicht mehr als 1 : 1. Und auslagern aus Model würde das Model etwas sehr entschlacken, da wär dann halt auch nix mehr drin.
-
Eisflamme schrieb:
Und auslagern aus Model würde das Model etwas sehr entschlacken, da wär dann halt auch nix mehr drin.
Macht ja nix.
"Weil Klasse X dann fast leer wird" kann ja wohl kein Argument sein.
-
Hi,
Mein Problem ist heute: Ich habe einen dicken Constructor.
Tja da hast du wohl Pech gehabt.
Mein Konstruktor is viel c00l3eR als deiner.
-
Aber die Klasse kapselt dann nur noch 1:1 die neue Zwischenklasse und ist damit nicht mehr als ein Wrapper. Ich finde schon, das verschiebt die Zustaendigkeiten zu sehr, zumal ich fuer jedes kleine Rendern dann zig Getter auf die andere Klasse nutzen muss. Klar, das geht alles. Aber da ist mir der Mehrwert durch die Zwischenklasse nicht gross genug, fuerchte ich.