Kompilierzeitprobleme mit modularer Programmierung
-
Danke für eure Antworten.
@ akari:
Was Forward Declarations betrifft: Da hab ich auch einige drin. Jedoch impliziert das immer, dass man als Member nur Zeiger oder Referenzen haben kann. Das führt dazu, dass ich für jede kleine Klasse, die mehrfach vorkommen kann, einen eigenen Kopierkonstruktor, Destruktor und Zuweisungsoperator implementieren muss - was meiner Ansicht nach nicht gerade optimal ist. Zudem hat man mit Zeigern eh viele Probleme, also wenn man etwas gerade so gut mit Stackvariablen erledigen kann, tut man das meiner Ansicht nach besser.Und in den Headerdateien hab ich eigentlich auch nur das Nötigste (eben die Klassendefinition selber + eventuelle globale Funktions- bzw. Variablendeklarationen).
@ audacia:
Aber das nützt doch auch nur, wenn man die Headerdateien nicht ändert. Bei einem Spielaufbau kommt es relativ häufig vor, dass man merkt "ah da muss ich noch eine Funktion hinzufügen, und dort fehlt ein spezifischer Konstruktor"...@ MFK:
Wie gesagt, bei meinem Design sehe ich keine grossen Möglichkeiten zur Optimierung mehr (zu Vorwärtsdeklarationen siehe oben).
Und weshalb wird die ausführbare Datei nicht stark grösser? Wird der Code vom Linker wieder schön zusammengeführt und vereinheitlicht? Sorry da kenn ich mich nicht sehr genau aus...Wie macht ihr das eigentlich?
#include-Direktiven fast nur in CPP-Dateien?
-
Nexus schrieb:
Jedoch impliziert das immer, dass man als Member nur Zeiger oder Referenzen haben kann. Das führt dazu, dass ich für jede kleine Klasse, die mehrfach vorkommen kann, einen eigenen Kopierkonstruktor, Destruktor und Zuweisungsoperator implementieren muss - was meiner Ansicht nach nicht gerade optimal ist. Zudem hat man mit Zeigern eh viele Probleme, also wenn man etwas gerade so gut mit Stackvariablen erledigen kann, tut man das meiner Ansicht nach besser.
Hast du dir das Pimpl-Idiom mal angesehen?
Nexus schrieb:
Aber das nützt doch auch nur, wenn man die Headerdateien nicht ändert. Bei einem Spielaufbau kommt es relativ häufig vor, dass man merkt "ah da muss ich noch eine Funktion hinzufügen, und dort fehlt ein spezifischer Konstruktor"...
Das klingt danach, als hättest du angefangen zu programmieren, bevor das Design fertig war. Das sollte nicht "relativ häufig" vorkommen, sondern ziemlich selten. Wenn du natürlich Planung und Implementierung "gleichzeitig" machst, solltest du dich über erhöhte Kompilierungszeiten nicht wundern.
Nexus schrieb:
Und weshalb wird die ausführbare Datei nicht stark grösser? Wird der Code vom Linker wieder schön zusammengeführt und vereinheitlicht?
Erklär doch mal, warum du glaubst, dass der Code überhaupt nennenswert größer werden sollte. Von Inline-Funktionen mal abgesehen, steckt doch jede Funktion in genau einer Übersetzungseinheit.
-
Nexus schrieb:
Wie macht ihr das eigentlich?
#include-Direktiven fast nur in CPP-Dateien?Sollte nicht durch saubere Trennung der Klassen mit Schnittstellen vermieden werden, dass du in Headerdateien andere Header einbindest?
In der Headerdatei sollte doch dann nur die Klasse als Schnittstelle stehen und bei der Implementation der Klassenmethoden (in einer cpp Datei) sollte dann auf andere Header verwiesen werden. Sonst bindest du den anderen Header immer mit ein, wenn du eine bestimmte Headerdatei einbindest, die andere Headerdateien includiert.
Also so weit ich weiß, sollte man #include-Direktiven möglichst nur in cpp-Dateien verwenden. Sonst mal das Design überdenken...
-
MFK schrieb:
Hast du dir das Pimpl-Idiom mal angesehen?
Ich hab es mir einmal angeschaut (auf en.wikibooks.org), aber irgendwie bin ich nicht so richtig davon überzeugt. Erstens müsste ich dann für jede Klasse noch eine separate mit dem Body erzeugen, und zweitens ist es gerade bei meinen Anwendungen nicht so extrem tragisch, wenn man als Anwender etwas sieht, das eigentlich zur Implementation gehört. Aber der Vorteil der Kompilierungsverkürzung ist natürlich schon vorhanden.
MFK schrieb:
Das klingt danach, als hättest du angefangen zu programmieren, bevor das Design fertig war. Das sollte nicht "relativ häufig" vorkommen, sondern ziemlich selten. Wenn du natürlich Planung und Implementierung "gleichzeitig" machst, solltest du dich über erhöhte Kompilierungszeiten nicht wundern.
Nein, das grobe Design stand eigentlich schon vorher. Ich weiss nicht, ob du selber schon Spiele programmiert hast, aber es kann schon vorkommen, dass man mitten in der Programmierung merkt, dass man noch ein neues Spielelement hinzufügen will, welches das halbe Spielkonzept über den Haufen wirft. Aber du hast Recht, häufig geschieht das eigentlich nicht.
MFK schrieb:
Erklär doch mal, warum du glaubst, dass der Code überhaupt nennenswert größer werden sollte. Von Inline-Funktionen mal abgesehen, steckt doch jede Funktion in genau einer Übersetzungseinheit.
Naja, ich war mir nur nicht sicher, weil ich mich eben nicht so gut auskenne bei der Codegenerierung und Linkage... Aber dann brauche ich mir ja keine Gedanken darum zu machen.
#include in Header schrieb:
Also so weit ich weiß, sollte man #include-Direktiven möglichst nur in cpp-Dateien verwenden. Sonst mal das Design überdenken...
Ich meine nur, wenn man z.B. eine Klasse hat, die als Member Objekte von anderen Klassen besitzt, dann kann man die benötigten Klassen doch gerade in dieser Headerdatei einbinden, oder?
Aber die ganze Thematik scheint mir doch schwerwiegender, als ich zu Beginn dachte. Früher hatte ich immer eine CPP-Datei, und alles andere in Header ausgelagert. Da brauchte ich mir weder Gedanken um Linker-Mehrfachdefinitionen, noch um erhöhte Kompilierzeiten, spezielles Einbinden von Headern oder Pimpl-Idioms zu machen. Klar war es vielleicht nicht das beste Design, aber immerhin kam man noch zum Programmieren

Es ist nicht so, dass ich den Vorteil der modular-objektorientierten Programmierung nicht sehe. Ich kann durchaus nachvollziehen, dass man bei wirklich grossen Projekten (z.B. in der Softwareentwicklung) grossen Nutzen davon hat, aber mir persönlich scheint es eher mehr Probleme zu bereiten...
-
Trotzdem kann ich bei dir nicht nachvollziehen, warum du bei modularer Programmierung größere Kompilierzeiten hast als bei einer Datei.
Denn bei Änderung an einer Datei müßte ja der ganze Code neu kompiliert werden (und dies jedesmal, anstatt nur einige wenige kleinere Dateien).Welchen Compiler setzt du denn ein, evtl. solltest du mal - wie vorgeschlagen - vorkompilierte Headerdateien einsetzen?
Vllt. könntest du ja mal dein Klassendesign exemplarisch hier aufzeigen, d.h. die Header und deren Includes.
Aus
Das führt dazu, dass ich für jede kleine Klasse, die mehrfach vorkommen kann, einen eigenen Kopierkonstruktor, Destruktor und Zuweisungsoperator implementieren muss - was meiner Ansicht nach nicht gerade optimal ist.
schließe ich, daß du noch einigen Nachholbedarf beim Design und deren Anwendung in C++ hast, denn das Verwenden von eingebetteten Objekten führt ja zu Kopien, so daß keine Eindeutigkeit der Objekte mehr gewährleistet ist.
Ein gutes Design führt oftmals insgesamt zu kleinerem Code (trotz expliziter Implementierung bestimmter Methoden).
Da ich 5 Jahre in einer Computerspiele-Firma gearbeitet habe, kenne ich mich mit den Problematiken bei großen Spieleprojekten sehr gut aus.
-
Th69 schrieb:
Trotzdem kann ich bei dir nicht nachvollziehen, warum du bei modularer Programmierung größere Kompilierzeiten hast als bei einer Datei.
Denn bei Änderung an einer Datei müßte ja der ganze Code neu kompiliert werden (und dies jedesmal, anstatt nur einige wenige kleinere Dateien).Ja, aber wie gesagt gibt es bei mir relativ viele Module, die voneinander abhängen (z.B. Spieler - Gegner, Spieler - Game, etc.)... Siehe dazu unten das Design.
Th89 schrieb:
Vllt. könntest du ja mal dein Klassendesign exemplarisch hier aufzeigen, d.h. die Header und deren Includes.
Gut möglich, dass das Design so nicht gut ist, aber damit hab ich eben noch nicht so grosse Erfahrung (bzw. das ist mein erstes Spiel, wo ich richtig modular programmiere und aufs Design achte).
Also es handelt sich um ein Jump'n'Run. Ich muss noch erwähnen, dass die Klassen
EnemyundWeaponnoch nicht implementiert sind, ich hab sie jedoch so im Design vorgesehen.Zuerst mal die Vererbungshierarchie:
[b]Master[/b] (verwaltet alles) im Moment noch alles statische Member, soll jedoch noch Singleton werden sozusagen global, von überall zugreifbar [b]Surface[/b] (Oberfläche) | +----------+------+-------+--------+ | | | | [b]MainMenu[/b] [b]InfoMenu[/b] .. [b]Game[/b] [b]Editor[/b] [b]Object[/b] (Spielelemente, [b]Tile[/b] die Kollision berücksichtigen) (Bausteine der Welt) | +--------------+---------------+ | | | [b]Player[/b] [b]Enemy[/b] [b]Weapon[/b] (abgefeuerte Waffen)Zusätzlich noch einige weniger relevante Klassen.
Hier die Membertypen von wichtigen Klassen:
Master --> Surface* (Basisklassenzeiger (Polymorphie)) --> Grafikklassen (Fenster, Event, etc.), die überall benötigt werden Game --> Player --> Enemy --> Weapon --> TileNun ist es so, dass Kollisionsabfragen in
Objectpassieren, d.h. da werden schon mal die KlassenTileundGame(aktuelle Oberfläche) benötigt. Bei den KlassenPlayer,EnemyundWeaponmuss ich wahrscheinlich eine Dreiecksabhängigkeit einrichten, weil sich diese Klassen jeweils gegenseitig beeinträchtigen können.
Gameselber benötigt natürlich alle Klassen, die Typen der Member sind.
Was ein Problem darstellt, ist die Tatsache, dass z.B.Playerindirekt über die BasisklasseObjectaufGameZugriff haben muss, um z.B. Kollisionsabfragen mit den Tiles zu handhaben. Ich habe das mit einem Zeiger gelöst, jedoch hab ich momentan das Problem, dass eine Vorwärtsdeklaration nicht ausreicht, weil eindynamic_cast<Game*>(Surface*)durchgeführt werden muss. Aber das lässt sich evtl. dadurch lösen, dass beim Erstellen derPlayer-Instanz ein Zeiger aufGameübergeben wird. Was wiederum nicht geht, weil im Konstruktor vonGameja auchPlayerkonstruiert wird, und derthis-Zeiger vonGamezu diesem Zeitpunkt noch auf die BasisklasseSurfaceverweist...
Grundsätzlich wäre fast alles mit Vorwärtsdeklarationen und Zeigern als Member zu lösen. Was ich jedoch unnötig finde, weil auf ich eigene Speicherverwaltung lieber verzichte, wo sichs vermeiden lässt (und wie gesagt auf The Big Three). Oder ist das die gängige Variante (Pimpl scheint mir arg übertrieben für mein verhältnismässig kleines Spiel)?
Dazu:
Th89 schrieb:
Aus
Das führt dazu, dass ich für jede kleine Klasse, die mehrfach vorkommen kann, einen eigenen Kopierkonstruktor, Destruktor und Zuweisungsoperator implementieren muss - was meiner Ansicht nach nicht gerade optimal ist.
schließe ich, daß du noch einigen Nachholbedarf beim Design und deren Anwendung in C++ hast
Das will ich auch nicht bestreiten

Aber was ist denn daran so falsch?
Und was meinst du genau mit folgender Aussage?Th89 schrieb:
denn das Verwenden von eingebetteten Objekten führt ja zu Kopien, so daß keine Eindeutigkeit der Objekte mehr gewährleistet ist.
-
Nexus schrieb:
Was Forward Declarations betrifft: Da hab ich auch einige drin. Jedoch impliziert das immer, dass man als Member nur Zeiger oder Referenzen haben kann. Das führt dazu, dass ich für jede kleine Klasse, die mehrfach vorkommen kann, einen eigenen Kopierkonstruktor, Destruktor und Zuweisungsoperator implementieren muss - was meiner Ansicht nach nicht gerade optimal ist.
Warum sollte man wegen Forward Declaration mehr Kopierkonstruktor, Destruktor und Zuweisungsoperator implementieren müssen? Thema Smartpointer...
shared_ptr<T> kann für einen unvollständigen Typ T verwendet werden:
class X; // forward declaration shared_ptr<X> pX; // "X *"Zum Dereferenzieren und Freigeben von pX muss X aber vollständig bekannt sein - boost prüft das und wirft andernfalls eine Compile-Fehlermeldung.
Nexus schrieb:
Wie macht ihr das eigentlich?
#include-Direktiven fast nur in CPP-Dateien?In meinen privaten Projekten: Ja.
In Projekten an der Arbeit: Kommt auf den Codestil an, auch wenn ich immer versuchen werde, soviel wie möglich in den Source zu ziehen.cu André
-
Nexus schrieb:
Master (verwaltet alles)
im Moment noch alles statische Member,
soll jedoch noch Singleton werden
sozusagen global, von überall zugreifbarHmm. Das würde ich schon mal anderst lösen. Mach da lieber ein Objekt draus, dass du dann direkt in der main initialisierst und hald einen Zeiger, oder eine Referenz an die restlichen Klassen übergibst, damit du darauf zugreifen kannst.
Fände ich sauberer, als da mit Singletons zu arbeiten.
-
drakon schrieb:
Nexus schrieb:
Master (verwaltet alles)
im Moment noch alles statische Member,
soll jedoch noch Singleton werden
sozusagen global, von überall zugreifbarHmm. Das würde ich schon mal anderst lösen. Mach da lieber ein Objekt draus, dass du dann direkt in der main initialisierst und hald einen Zeiger, oder eine Referenz an die restlichen Klassen übergibst, damit du darauf zugreifen kannst.
Fände ich sauberer, als da mit Singletons zu arbeiten.
Das hättest du anders zitieren sollen. So macht es den Eindruck, als wollte Nexus sich als Dichter betätigen.
-
camper schrieb:
Das hättest du anders zitieren sollen. So macht es den Eindruck, als wollte Nexus sich als Dichter betätigen.

Entweder ich verstehe dich falsch, oder du hast ein Smylie vergessen.
-
drakon schrieb:
Fände ich sauberer, als da mit Singletons zu arbeiten.
Ich finde das hört sich schon ziemlich nach Singleton an. Überall Zeiger rumzureichen finde ich da viel viel ekliger.
Nexus schrieb:
Grundsätzlich wäre fast alles mit Vorwärtsdeklarationen und Zeigern als Member zu lösen.
Mach das bloß nicht. Ist imo unsinning, genauso mit Smartpointern. Ich würde nie "reine" Membervariablen gegen Kompilierzeit eintauschen.
-
drakon schrieb:
camper schrieb:
Das hättest du anders zitieren sollen. So macht es den Eindruck, als wollte Nexus sich als Dichter betätigen.

Entweder ich verstehe dich falsch, oder du hast ein Smylie vergessen.
Du irrst, indem du recht hast.
-
camper schrieb:
drakon schrieb:
camper schrieb:
Das hättest du anders zitieren sollen. So macht es den Eindruck, als wollte Nexus sich als Dichter betätigen.

Entweder ich verstehe dich falsch, oder du hast ein Smylie vergessen.
Du irrst, indem du recht hast.

-
camper schrieb:
Das hättest du anders zitieren sollen. So macht es den Eindruck, als wollte Nexus sich als Dichter betätigen.
Dass man es als Gedicht erkennen könnte, war eigentlich nie meine Absicht - aber meine stichwortartige Beschreibung kommt da halt schon nahe

Badestrand schrieb:
drakon schrieb:
Fände ich sauberer, als da mit Singletons zu arbeiten.
Ich finde das hört sich schon ziemlich nach Singleton an. Überall Zeiger rumzureichen finde ich da viel viel ekliger.
Nexus schrieb:
Grundsätzlich wäre fast alles mit Vorwärtsdeklarationen und Zeigern als Member zu lösen.
Mach das bloß nicht. Ist imo unsinning, genauso mit Smartpointern. Ich würde nie "reine" Membervariablen gegen Kompilierzeit eintauschen.
Puh, endlich mal jemand, der das Ganze so sieht wie ich

Was das Zeiger-Rumreichen betrifft, wäre das wirklich total eklig, da dann jede Funktion, die entweder Eingaben oder Bildschirmausgaben handhabt, noch einen zusätzlichen Parameter hätte (oder jede Klasse noch einen zusätzlichen Zeiger). Zum Teil wären auch noch ganz andere Funktionen betroffen, weil z.B. auch eine Instanz einer Zufallsgeneratorklasse inMastersteht. Das würde sich hinten und vorne nicht lohnen.Naja, ich hab mich einfach gewundert; wenn man beginnt, modular zu programmieren, sind plötzlich wieder Unmengen "Hilfsmittel" erforderlich, dass man es nur schafft, nicht schlechtere Voraussetzungen als bei nicht-modularer Programmierung zu haben... Wahrscheinlich nehm ich doch lieber die längere Kompilierzeit in Kauf...
Ach ja, noch was: Gibt es so was wie meine Masterklasse häufig in der Praxis? Ich finde es sehr praktisch, da ich in
main()gerade mal zwei Anweisungen habe. Das Unschöne ist vielleicht, dassMasternur statische Member und Methoden hat, aber wie gesagt mach ich da vielleicht noch ein Singleton draus.
-
Nexus schrieb:
Naja, ich hab mich einfach gewundert; wenn man beginnt, modular zu programmieren, sind plötzlich wieder Unmengen "Hilfsmittel" erforderlich, dass man es nur schafft, nicht schlechtere Voraussetzungen als bei nicht-modularer Programmierung zu haben... Wahrscheinlich nehm ich doch lieber die längere Kompilierzeit in Kauf...
Was du aber bedenken solltest: In der Anfangsphase wo man viele Interfaces aendert und oft nur mit mockups herumwirft und jedweder code total instabil (aus aenderungssicht - nicht bug-sicht) ist, sind mehrere uebersetzungseinheiten natuerlich doof, da so das linken laenger dauert.
sobald du aber einen stand erreicht hast wo ein teil des codes interface-stabil ist, sinken die compiletimes rapide. denk einfach nur daran, dass du ja einen grossteil der dateien nachdem sie einmal fertig sind kaum noch anfasst. am anfang, wo du aber jede datei dauernd aenderst - ists natuerlich etwas stoerend.
und auch aktuell, also trotz grossem recompile kann ich mir nicht vorstellen dass die compilezeiten soviel groesser sind. denn alles was mehr gemacht wird sind mehrmals header parsen -> und das laesst sich mit precompiled headers sogar umgehen.
wenn du compilezeiten extrem senken willst, dann schau dir das pimpl-idiom an. ich bin kein grosser fan davon, wenn es aber um reduzierung von abhaengigkeiten geht ist es einfach super.
20 sekunden recompile deutet auf entweder einen sehr langsamen rechner hin oder auf viel code. und bei viel code: warum aenderst du die zentralen stellen dauernd?
-
und bei viel code: warum aenderst du die zentralen stellen dauernd?
:p
Weisst du, was mir da manchmal passiert?
Ich schaue mal ein wenig im Core, ob alles stimmt usw. Und aus reiner Gewohnheit mache ich da auch aus dem Reflex raus ctrl+s.. Und ich bemerke meinen Fehler auch sogleich beim drücken. *grml*
Lösung: ctrl+F7 und mal was trinken gehen.
-
drakon schrieb:
und bei viel code: warum aenderst du die zentralen stellen dauernd?
:p
Weisst du, was mir da manchmal passiert?
Ich schaue mal ein wenig im Core, ob alles stimmt usw. Und aus reiner Gewohnheit mache ich da auch aus dem Reflex raus ctrl+s.. Und ich bemerke meinen Fehler auch sogleich beim drücken. *grml*
Lösung: ctrl+F7 und mal was trinken gehen.
dann ist dein editor komisch... wenn es keine aenderung gibt, sollte er die datei nicht modifizieren, auch bei einem ctrl+s nicht...
aber klar, sowas passiert mal dass man aus versehen eine zentrale datei unnoetigerweise "aendert" - aber das ist hoffentlich ausnahme und nicht regel

-
dann ist dein editor komisch... wenn es keine aenderung gibt, sollte er die datei nicht modifizieren, auch bei einem ctrl+s nicht...
aber klar, sowas passiert mal dass man aus versehen eine zentrale datei unnoetigerweise "aendert" - aber das ist hoffentlich ausnahme und nicht regel
Meist ist hald so, dass ich dennoch eine Taste drücke und dann speichere..

Ja, ich schaue schon, dass das nicht all zu oft passiert. Es ist in der Tat recht mühsam nacher alles neu compilen zu müssen..

-
Nexus schrieb:
Nun ist es so, dass Kollisionsabfragen in
Objectpassieren, d.h. da werden schon mal die KlassenTileundGame(aktuelle Oberfläche) benötigt.Also wenn schon würde ich Tile auch von Object ableiten und somit Tile auch als Kollisionobjekt ansehen. In der Klasse Object gibt es eine Methode, welche ein anderes Object annimmt und dann die Kollisionsabfrage ausführt.
Nexus schrieb:
Bei den Klassen
Player,EnemyundWeaponmuss ich wahrscheinlich eine Dreiecksabhängigkeit einrichten, weil sich diese Klassen jeweils gegenseitig beeinträchtigen können.Wird wohl nicht nötig sein, zumindest nicht in den Headern. Da kannst du überall Forward Declaration anwenden.
Du wirst ja wohl kaum ein Player, Enemy oder Weapon kopieren wollen. Von überall her, wo du eine Instanz auf den Player, Enemy oder Weapon übergeben tust, soll es ja auch die gleiche sein. Also wirst du in erster Linie Zeiger herumreichen. Vor allem auch, weil du die Spieler, Gegner und Waffen auf dem Heap erstellen wirst. Und es sicher Möglichkeiten gibt, dass es keine Gegner, keine Waffen oder keine Spieler hat, also Nullzeiger.Nexus schrieb:
Gameselber benötigt natürlich alle Klassen, die Typen der Member sind.Kommt drauf an, wie zentral du Game gestaltest. Vergiss nicht, dass man in Module auslagern sollte. Man kann auch Funktionsweisen oder Handlungen in Objekte packen, auch Beschreibungen sind möglich. Oder anders ausgedrückt:
Nicht nur Nomen sind Objekte, sondern auch Adjektive, Verben, Adverben, Pronomen und was es sonst noch alles gibt
Nexus schrieb:
Was ein Problem darstellt, ist die Tatsache, dass z.B.
Playerindirekt über die BasisklasseObjectaufGameZugriff haben muss, um z.B. Kollisionsabfragen mit den Tiles zu handhaben.Das leuchtet mir überhaupt nicht ein. Wieso kann der Player nicht direkt mit den Tiles interagieren? Game sollte höchstens den Player verschieben oder sowas ähnliches.
Nexus schrieb:
Was wiederum nicht geht, weil im Konstruktor von
Gameja auchPlayerkonstruiert wird, und derthis-Zeiger vonGamezu diesem Zeitpunkt noch auf die BasisklasseSurfaceverweist...
Ah, da haben wir wieder so ein Problem. Du erstellst den Player als Eigenschaft von Game. Aber ist er das wirklich? Ich finde das irgendwie nicht. Einem Game Objekt sollte ein Player Objekt zugewiesen sein, vielleicht auch mehrere, aber es sollte den Player sicher nicht besitzen.
Ein Spieler sollte meiner Meinung nach anderswo erstellt und dem Spiel dann übergeben werden.Nexus schrieb:
Grundsätzlich wäre fast alles mit Vorwärtsdeklarationen und Zeigern als Member zu lösen. Was ich jedoch unnötig finde, weil auf ich eigene Speicherverwaltung lieber verzichte, wo sichs vermeiden lässt (und wie gesagt auf The Big Three).
Auf "The Big Three" verzichtest du, in dem du Zeiger verwendest. Aber wenn du möglichst Zeiger meiden willst, weil du Angst vor der eigenen Speicherverwaltung hast, dann solltest du vielleicht eine andere Sprache wählen? C# oder Java? Bei C++ sind ja gerade solche Dinge ganz entscheidend und vieles wird darüber aufgebaut. Man verzichtet einfach auf einen ganz entscheidenden Teil der Sprache, wenn man keine eigene Speicherverwaltung einsetzt. Shared Pointer und ähnliches sollten nur Hilfskonstrukte für gewisse Situationen sein, aber nicht für alles.
Ich hoffe ich konnte dir etwas helfen. Vor allem überwinde deine Angst vor der Speicherverwaltung!
Eine "Master-Option"-Klasse, mache ich höchst selten. Das hat den einfach Grund, dass wenn man die Sache genügend aufsplittet in Objekte und Module, es irgendwann keine Option geben wird, welche für alle oder mehr als die Hälfte gilt. Man sollte zudem, meiner Meinung nach, nicht in solchen Dimensionen denken. Wenn eine Option verändert wird, dann betrifft das direkt entsprechende Objekte und dort sollten die Eigenschaften entsprechend gesetzt werden. Man sollte nicht Optionen zusammenschaufeln und in eine gemeinsame Klasse verfrachten. Ausser das sie Optionen sind, haben diese nämlich nichts gemeinsam!
Grüssli
-
Vielen Dank für die hilfreichen Antworten! Es ist wirklich nett, wie ihr euch Zeit nehmt!
Shade Of Mine schrieb:
Was du aber bedenken solltest: In der Anfangsphase [...] sind mehrere uebersetzungseinheiten natuerlich doof, da so das linken laenger dauert.
sobald du aber einen stand erreicht hast wo ein teil des codes interface-stabil ist, sinken die compiletimes rapide.Hmm, das hat in der Tat was, daran hab ich eigentlich gar nicht gedacht

Shade Of Mine schrieb:
20 sekunden recompile deutet auf entweder einen sehr langsamen rechner hin oder auf viel code. und bei viel code: warum aenderst du die zentralen stellen dauernd?
Nein, ich denke nicht, dass der Rechner zu langsam ist. Eher die zweite Möglichkeit. Und wie gesagt lassen sich häufige Änderungen nicht immer umgehen, weil man plötzlich Details bemerkt oder neue Features hinzufügen will, die man vorher noch nicht eingeplant hat. Aber es stimmt schon, ich sollte mich vielleicht ein bisschen einschränken mit zentralen Änderungen.
drakon schrieb:
Lösung: ctrl+F7 und mal was trinken gehen.

Hast du demnach auch das Problem mit langen Kompilierzeiten? Oder sind deine Projekte einfach so riesig?

Dravere schrieb:
Also wenn schon würde ich Tile auch von Object ableiten und somit Tile auch als Kollisionobjekt ansehen. In der Klasse Object gibt es eine Methode, welche ein anderes Object annimmt und dann die Kollisionsabfrage ausführt.
Nein, dann würde es von der Logik her keinen Sinn mehr machen. Ein Tile ist ja ein fester Bestandteil der Welt, d.h. es kann nur rasterförmig ausgerichtet sein (eben in einer Tilemap) und bewegt sich nicht. Objekte wie der Spieler oder abgefeuerte Waffen hingegen sind mobil und reagieren auf Kollision mit der Welt (also den Tiles).
Dravere schrieb:
Nexus schrieb:
Bei den Klassen
Player,EnemyundWeaponmuss ich wahrscheinlich eine Dreiecksabhängigkeit einrichten, weil sich diese Klassen jeweils gegenseitig beeinträchtigen können.Wird wohl nicht nötig sein, zumindest nicht in den Headern. Da kannst du überall Forward Declaration anwenden.
Du wirst ja wohl kaum ein Player, Enemy oder Weapon kopieren wollen. Von überall her, wo du eine Instanz auf den Player, Enemy oder Weapon übergeben tust, soll es ja auch die gleiche sein. Also wirst du in erster Linie Zeiger herumreichen. Vor allem auch, weil du die Spieler, Gegner und Waffen auf dem Heap erstellen wirst. Und es sicher Möglichkeiten gibt, dass es keine Gegner, keine Waffen oder keine Spieler hat, also Nullzeiger.Ja, also momentan verwalte ich diese Klassen in STL-Containern innerhalb von
Game(die eben leer sind falls kein entsprechendes Objekt existiert). Falls es etwas rumzureichen gibt, benutze ich eher Referenzen. Aber das muss ich eh noch ausbauen, jetzt wird z.B. auf die Tiles statt einfacher Übergabe relativ kompliziert zugegriffen -dynamic_cast<Game*>(Master::MySurface)->Tiles(natürlich noch abgekürzt)
Dravere schrieb:
Nexus schrieb:
Was ein Problem darstellt, ist die Tatsache, dass z.B.
Playerindirekt über die BasisklasseObjectaufGameZugriff haben muss, um z.B. Kollisionsabfragen mit den Tiles zu handhaben.Das leuchtet mir überhaupt nicht ein. Wieso kann der Player nicht direkt mit den Tiles interagieren? Game sollte höchstens den Player verschieben oder sowas ähnliches.
Wie ich schon weiter oben angedeutet habe, hab ich das momentan noch relativ kompliziert (eher global) gelöst. Aber wenn der Spieler direkt mit den Tiles interagieren sollte, müsste ja bei dessen Erstellung im Konstruktor eine Referenz auf die Tiles übergeben werden, und auf Gegner genauso. Gegner wiederum hätten Referenzen auf Spieler und Tiles, was zu Konflikten führt, wenn eines der Objekte noch nicht konstruiert wurde und eine Referenz darauf übergeben wird.
Dravere schrieb:
Nexus schrieb:
Gameselber benötigt natürlich alle Klassen, die Typen der Member sind.Kommt drauf an, wie zentral du Game gestaltest.
Dravere schrieb:
Nexus schrieb:
Was wiederum nicht geht, weil im Konstruktor von
Gameja auchPlayerkonstruiert wird, und derthis-Zeiger vonGamezu diesem Zeitpunkt noch auf die BasisklasseSurfaceverweist...
Ah, da haben wir wieder so ein Problem. Du erstellst den Player als Eigenschaft von Game. Aber ist er das wirklich? Ich finde das irgendwie nicht. Einem Game Objekt sollte ein Player Objekt zugewiesen sein, vielleicht auch mehrere, aber es sollte den Player sicher nicht besitzen.
Ein Spieler sollte meiner Meinung nach anderswo erstellt und dem Spiel dann übergeben werden.Daran hab ich auch relativ lange überlegt. Ich hab auch eine Klasse
Mapin Betracht gezogen (für ein Level/eine Karte). Schlussendlich hab ich mich fürGameentschieden, weil das so schön zu der Polymorphie mit denSurfaces (verschiedene Menüoberflächen) passt (Bei mir ist es so, dass es verschiedene gleichwertige Oberflächen gibt (Hauptmenü, Info, Hilfe, Editor, Game)). Zudem ist es auch logisch sinnvoll, wenn Spieler, Gegner, Tiles gerade erstellt werden, wenn man das Spiel startet (also eine Instanz vonGameerstellt). Von mir aus gesehen macht es keinen Sinn, Spieler etc. separat zu erstellen und anGamezu übergeben, weil sie ja unmittelbar daran gebunden sind (welchen Sinn hat ein Spieler ohne Spiel?)
Dravere schrieb:
Auf "The Big Three" verzichtest du, in dem du Zeiger verwendest.
Ich meinte, ich müsse Copy-Ctor, Dtor und Op= selber implementieren, wenn ich Zeiger als Member habe - was ich bei Stackvariablen nicht muss.
Dravere schrieb:
Aber wenn du möglichst Zeiger meiden willst, weil du Angst vor der eigenen Speicherverwaltung hast, dann solltest du vielleicht eine andere Sprache wählen? C# oder Java? Bei C++ sind ja gerade solche Dinge ganz entscheidend und vieles wird darüber aufgebaut. Man verzichtet einfach auf einen ganz entscheidenden Teil der Sprache, wenn man keine eigene Speicherverwaltung einsetzt. Shared Pointer und ähnliches sollten nur Hilfskonstrukte für gewisse Situationen sein, aber nicht für alles.
Vielleicht hab ich einen falschen Eindruck erweckt, es ist ja nicht so, dass ich ängstlich vor Zeigern und eigener Speicherverwaltung zurückschrecke - ich hab immer noch genügend Orte, wo ich mit
newunddeletearbeite
Ich meinte vielmehr, dass eigene Speicherverwaltung halt viele Fehlerrisiken birgt und man viel stärker aufpassen muss. Ich sehe deshalb automatische Speicherverwaltung als "Geschenk" an, das auch eingesetzt werden sollte, wenn es geht (im Übrigen ist es auch noch schneller). Wie Badestrand in seinem Post gesagt hat, würde ich Stackvariablen gegen "praktische" Vorwärtsdeklarationen lieber nicht eintauschen.Dravere schrieb:
Eine "Master-Option"-Klasse, mache ich höchst selten. Das hat den einfach Grund, dass wenn man die Sache genügend aufsplittet in Objekte und Module, es irgendwann keine Option geben wird, welche für alle oder mehr als die Hälfte gilt. Man sollte zudem, meiner Meinung nach, nicht in solchen Dimensionen denken. Wenn eine Option verändert wird, dann betrifft das direkt entsprechende Objekte und dort sollten die Eigenschaften entsprechend gesetzt werden. Man sollte nicht Optionen zusammenschaufeln und in eine gemeinsame Klasse verfrachten. Ausser das sie Optionen sind, haben diese nämlich nichts gemeinsam!
Hm... Was meinst du genau mit "Optionen"? Ich hab das Gefühl, wir stellen uns unter einer Masterklasse nicht ganz das Gleiche vor.
Bei mir ist es so, dass nur die ganz zentralen Dinge dort geregelt werden. Der Master ist bezeichnenderweise (sorry, wusste keinen besseren Namen als "Master" :)) die oberste Direktive, die alles regelt. Er überwacht den Verlauf des Programms und gibt den einzelnen Oberflächen (abstrakte BasisklasseSurface) die Kontrolle, wenn eine entsprechende Eingabe getätigt wurde. Abgesehen von der Kontrollfunktion besitzt er noch einige für Grafikroutinen benötigte Variablen (z.B. das Fenster, oder Event für Eingaben, Schriftarten), dient also auch als Ersatz für globale Variablen.Aber ganz dezentral kannst du das Programm ja auch nicht steuern (ich nehme nicht an, dass du sehr vieles in der
main()-Funktion stehen hast), oder? Ich hab bei mir gerade mal zwei Funktionen inmain(), nämlich eine für die Initialisierung und eine für den ständigen Verlauf.So, wieder viel geschrieben

Ich hoffe, ihr könnt meine Sichtweise und mein Design einigermassen nachvollziehen - bei irgendwelchen Unklarheiten sofort fragen