Alle Klassen auf bestimmte Variable angewiesen
-
Hallo,
bei meiner 3D-Engine sind die meisten Klassen auf die globale Variable device angewiesen:
IDirect3DDevice9 *device;
Natürlich sind globale Variablen nicht unbedingt ein guter Programmierstil.
Was haltet ihr davon, dass man bei jeder Klasse die Variable "registrieren" muss und die dann von der Klasse abgespeichert wird?
z.B. so:xyz.SetDevice(device);
Es erscheint mir aber irgendwie sinnlos, dass sich jede Klasse dieselbe Variable "merken" muss.
Kann ich auch einfach auf die Variable device zugreifen, und dann einfach festlegen dass jedes Programm, das die Engine verwendet, seine Device "device" nennen muss?
Wie kann man das Problem am saubersten lösen?
mfg. Tubos
-
Wie kann man das Problem am saubersten lösen?
Das muss eigentlich jeder für sich selbst entscheiden, was die beste Lösung darstellt.
Bei mir z.B. enthalten die Klassen gar keinen API abhängigen Quellcode und erledigen ihre Arbeit über ausgelagerte Hilfsfunktionen, die dann die Arbeit mit dem Device übernehmen.Wie wärs denn, wenn du den Devicepointer dem Konstruktor übergibst?
-
Naja, warum muß denn jede Klasse diese Varaible kennen? Wofür ist denn diese Variable? Ist sowas wie ein 3D-Screen? Falls ja, dann ist doch dein Design irgendwie falsch. Weil dann hast du bisher noch keine Klasse, die sich ausschliesslcih mit dem 3D-Screen beschäftigt. Das ganze Kapselungs-Konzept hättest du somit nicht beachtet.
Hast du nicht sowas:
class 3d_screen { public: void drawPoly(polygon &p); private: IDirect3DDevice9 *device; };
Warum müssen in dem Fall alle anderen Klassen IDirect3DDevice9 kennen??? Es wäre viel besser, wenn du DirectX-Sachen weg kapselst.
Und wer muß 3d_screen kennen? In dem Fall würde man z.B. eine Klasse 3d_scene haben, die die ganzen Polygone oder 3D-Objekte (die Polygone haben) verwaltet. Und 3d_scene würde 3d_screen kennen und dieser nur die Polygone/3d-Objekte übergeben.
Klar, irgenwie muß man immer Instanzen von Objekten weiterreichen und diese bekannt geben. Aber es lohnt sich eher Aufgaben zu verteilen. 3d_screen könnte z.B. nicht nur *device haben, sondern auch noch andere relevante Variablen. Da lohnt es sich gleich eher 3d_screen woanders bekannt zu machen. Mit einem 3d_screen-Pointer oder -Reference hätte man mehrere Variablen "erschlagen".
Warum hast du *device überhaupt global? *device ist doch nicht gleich zum Programmstart initialisiert. Du mußt doch auch sowas machen:
IDirect3DDevice9 *device = GetIDirect3DDevice9();
Oder so ähnlich. Warum machst du das nicht so:
class 3d_screen { public: 3d_screen(); void drawPoly(polygon &p); private: IDirect3DDevice9 *device; }; // in cpp-Datei: 3d_screen::3d_screen() { device = GetIDirect3DDevice9(); }
-
Zur Funktionsweise sollte ich vielleicht auch noch was sagen:
Das Ganze besteht aus 3D-Szenen (Klasse: C3DScenario). Von C3DScenario leite ich die einzelnen Szenen ab und füge dabei Methoden wie Render(), Input(), etc... hinzu. Diese abgeleiteten Klassen enthalten 3D-Objekte (Klasse: C3DObject).Naja, warum muß denn jede Klasse diese Varaible kennen? Wofür ist denn diese Variable?
Über den Objektpointer device kann ich z.B. Objekte zeichnen und Rendereinstellungen festlegen. Einstellungen festlegen müssen z.B. die Klasse für die 3D-Szene, die 3D-Objekt-Klasse, die Kamera-Klasse, ...
Weil dann hast du bisher noch keine Klasse, die sich ausschliesslcih mit dem 3D-Screen beschäftigt.
IDirect3DDevice9 ist ja bereits eine Klasse, und ich sehe im Moment keinen Sinn dahinter, die nochmals zu kapseln (ich lasse mich gerne eines besseren belehren
).
Und selbst wenn ich sie kapsle bleibt noch die Frage, wie die anderen Klassen darauf zugreifen sollen. Ja, ich bin OOP-Anfänger
Wäre so etwas in Ordnung?// global CMyDevice *device; // irgendwo während der initialisierung device=new CMyDevice(...); // Irgendwo im Code von C3DObject, in einer anderen Datei extern CMyDevice device; ... device->DoSomething(...);
Das bedeutet dann: immer wenn ich die Engine von einem Programm aus verwende muss ich eine Instanz von CMyDevice erstellen und diese muss "device" heißen. Andernfalls funktioniert das nicht.
Wie wärs denn, wenn du den Devicepointer dem Konstruktor übergibst?
Das ist vom Prinzip her eine sehr gute Idee!
Und ist da nichts dagegen zu sagen dass jede Klasse das device selbst speichern muss? Das klingt irgendwie sinnlos
-
Tubos schrieb:
Naja, warum muß denn jede Klasse diese Varaible kennen? Wofür ist denn diese Variable?
Über den Objektpointer device kann ich z.B. Objekte zeichnen und Rendereinstellungen festlegen. Einstellungen festlegen müssen z.B. die Klasse für die 3D-Szene, die 3D-Objekt-Klasse, die Kamera-Klasse, ...
Würde ich trotzdem kapseln.
Tubos schrieb:
Weil dann hast du bisher noch keine Klasse, die sich ausschliesslcih mit dem 3D-Screen beschäftigt.
IDirect3DDevice9 ist ja bereits eine Klasse, und ich sehe im Moment keinen Sinn dahinter, die nochmals zu kapseln (ich lasse mich gerne eines besseren belehren
).
Was ist wenn du mal auf DirectX10 umsteigen willst? Willst du dann alle Typ-Namen (wie eben IDirect3DDevice9) mit Text-Suchen-Ersetzen ändern? Wenn du kapselst, mußt du später nur an EINER Stelle die Typnamen, die Methodennamen usw. ändern. DirectX ist eine eher lowlevel-API, sowas kapselt man eigentlich.
Tubos schrieb:
Und selbst wenn ich sie kapsle bleibt noch die Frage, wie die anderen Klassen darauf zugreifen sollen. Ja, ich bin OOP-Anfänger
Wäre so etwas in Ordnung?// global CMyDevice *device; // irgendwo während der initialisierung device=new CMyDevice(...); // Irgendwo im Code von C3DObject, in einer anderen Datei extern CMyDevice device; ... device->DoSomething(...);
Das bedeutet dann: immer wenn ich die Engine von einem Programm aus verwende muss ich eine Instanz von CMyDevice erstellen und diese muss "device" heißen. Andernfalls funktioniert das nicht.
Warum denn schon wieder global???
int main() { CMyDevice device; // Konstruiert hier bereits CMyDevice! device.DoSomething(); }
Nichts mit global!!!
So, wie geht es weiter? Normalerweise kannst du das unterschiedlich angehen. Du könntest sagen, das ein erstes Spielelevel gestartet werden soll... als Beispiel:
int main() { CMyDevice device; Level level(device); // Level bekommt das Device übergeben. level.loadLevel(1); // Lädt das erste Level level.start(); // in start wird die Render(Level) aufgerufen... s.u. }
Jetzt muß CMyDevice natürlich eine render-Methode anbieten. Level weiß aber nichts von IDirect3DDevice9. Das ist auch gut so! Denn CMyDevice kennt es ja und das reicht:
void CMyDevice::Render(const Level &level) { device->BeginScene(); MyTransform *tf = level.getTransform(); // Alle Infos kapseln!!! device->SetTransform(tf->getState(), tf->getMatrix()); // hier könntest du nun aus dem Level alle 3D-Objekte holen und zeichnen. // Wobei nur CMyDevice die Renderroutinen aufruft. Level weiß nicht wie // gezeichnet wird! Ist garnicht dessen Aufgabe. // Oder du hast eine MyLevelRenderer-Klasse, der du this und level // übergibts MyLevelRenderer lr(this, level); lr.render(); device->EndScene(); }
MyTrasform hätte dann halt alle wichtigen Trasformations-Variablen und Methoden. Aber die Interfaces deiner Klassen nehmen nur deine Typen (Level, MyTrasform, MyTexture usw.) entgegen. In der Implementierung jedoch kannst dann den DirecX-Kram verstecken und abarbeiten.
Du siehst, IDirect3DDevice9 muß nur in einer Klasse bekannt sein. Die anderen Objekte interessiert es nicht. Später muß du dann bei DX10 nur in CMyDevice EINE Änderung machen... Level interessiert es einfach nicht ob das DX10 oder DX9 ist.
Thats OO.
-
Und was mach ich jetzt mit meiner Objekt-Klasse?
Normalerweise ist die für das Rendern des Objektes verantwortlich:object.Render();
Ich weiß nicht ganz ob ich dein Beispiel verstanden habe.
Soll ich der Render-Methode bei jedem Aufruf eine Referenz auf die Device-Klasse übergeben?object.Render(MyDevice);
void C3DObject::Render(CMyDevice &MyDevice) { MyDevice.SetRenderState(...); MyDevice.DrawPrimitive(...); ... }
-
Genau! Das wäre eine Möglichkeit, die ich z.B. auch bevorzugen würde. So kann jedes 3D-Objekt sich selbst zeichnen, in dem es MyDevice zur Hilfe nimmt. Trotzdem weiß dein 3D-Objekt nichts vom IDirect3DDevice9 und alle sind happy. Es ist immer besser wenn man von Außen nur sein eigenes Framework/Engine sieht. Die Implementierung dagegen muß natürlich irgendwann auf eine niedrigere API zugreifen - klar.
-
Sehr gut!
Habe ich noch Performanceeinbußen zu befürchten wenn ich Inline-Funktionen verwende oder geht das dann genau so schnell wie wenn MyDevice global definiert wäre?
-
Globale und nicht-globale Variablen haben absolut nichts mit Performance zu tun. Haben nur etwas mit Gültigkeitsbereiche zu tun, mehr nicht. Kann dich ansonst gerne auch auf meine Homepage für weitere Infos bzgl. Variablen und Resourcen verweisen. Vielleicht findest du da noch nützliche Infos?
-
Globale und nicht-globale Variablen haben absolut nichts mit Performance zu tun.
Das ist mir schon klar.
Es geht mir nur darum: Wenn ich das Rendern eines Objektes als normale Funktion implementiere, muss jedesmal ein Zeiger auf das Objekt übergeben werden. Wenn das Device bereits irgendwo definiert ist - möglicherweise auch im Objekt selbst gespeichert - entfällt die Übergabe.
Wenn ich die Funktion inline mache, dann muss ja auch nichts übergeben werden. (oder doch?)
-
Du meinst sowas:
inline void render(3DObjekt &obj) { }
Wenn du nicht gerade einen Steinzeitcompiler hast, dann würde ich mir um solche Dinge keine Gedanken machen. Selbst wenn du inline angibst, entscheidet immer noch der Compiler ob er es wirklich inline macht. Meistens muß man nicht mal inline vorgeben, und der Compiler kann dass sogar von sich aus inline machen, wenn er der Meinung ist, das dies von Vorteil wäre.
Wichtig ist eher, das du bei den Parametern nicht sowas machst:
void render(3DObjekt obj) { }
Weil dann macht er eine Kopie deines Objekts. Und wenn dieses z.B. 100 KB groß ist (nur mal als Beispiel), dann macht er 100%ig eine 100 KB Kopie. Das ist schlimmer als ob etwas inline oder nicht inline ist.
Dann lieber by-Reference, am besten noch const, dann haste wirklich das beste vorgegeben, und den Rest lass mal den super schlauen Compiler machen. Z.B. so:
void render(const 3DObjekt &obj) { }
Außerdem kann man auch Performace sparen, in dem man wenig Parameter in der Signatur hat. Deshalb sind structs und classes immer besser als mehrere lose Variablen. Weil viele Parameter in einer Signatur bedeutet auch viel mehr Aufwand beim Aufruf. Aber ich rede jetzt nicht von zwei oder drei Parameter. Aber es soll ja Schlauberger geben, die 10 Parameter in einer Signatur haben... von der Unübersichtlichkeit für den Programmierer ganz zu schweigen.
-
Meistens muß man nicht mal inline vorgeben, und der Compiler kann dass sogar von sich aus inline machen, wenn er der Meinung ist, das dies von Vorteil wäre.
Ich hab die Autorenversion von Visual C++ 6.0, und der Compiler von der Autorenversion optimiert nicht.
Soviel ich weiß kann er zwar entscheiden, ob er eine als inline deklarierte Funktion wirklich inline macht, aber von sich aus inlinen tut der Compiler nicht.
Und die Enterprise, die ich mir mal von einem Freund ausgeborgt habe, kann ich nicht installieren weil das Setup-Programm bei der Installation abstürzt. Ich probiere es aber soeben noch einmal...
hey, jetzt scheint es zu funktionieren!!!Und wenn dieses z.B. 100 KB groß ist (nur mal als Beispiel), dann macht er 100%ig eine 100 KB Kopie. Das ist schlimmer als ob etwas inline oder nicht inline ist.
Schon klar. Ich übergebe nie ganze Klassen.
Danke für eure Antworten, das hat mir wirklich weitergeholfen.
Ich mach mich erstmal daran dass ich den vorhandenen Code vollständig objektorientiert gestalte.mfg. Tubos