Ich arbeite an meiner eigenen Spiel-Engine und entwerfe derzeit meine Manager. Ich habe gelesen, dass für die Speicherverwaltung die Verwendung von Init()
und CleanUp()
Funktionen besser sind als die Verwendung von Konstruktoren und Destruktoren.
Ich habe nach C ++ - Codebeispielen gesucht, um zu sehen, wie diese Funktionen funktionieren und wie ich sie in meine Engine implementieren kann. Wie funktioniert Init()
und CleanUp()
wie kann ich sie in meine Engine implementieren?
Antworten:
Eigentlich ist es ziemlich einfach:
Anstatt einen Konstruktor zu haben, der Ihr Setup ausführt,
... lassen Sie Ihren Konstruktor wenig oder gar nichts tun und schreiben Sie eine Methode namens
.init
oder.initialize
, die das tut, was Ihr Konstruktor normalerweise tun würde.Also jetzt anstatt einfach so zu gehen wie:
Du kannst gehen:
Der Vorteil besteht darin, dass Sie die Abhängigkeitsinjektion / -inversion der Steuerung jetzt einfacher in Ihren Systemen verwenden können.
Anstatt zu sagen
Sie können den Soldaten bauen, geben Sie ihm ein Ausrüsten Verfahren, in dem Sie die Hand ihm eine Waffe, und dann der ganze Rest der Konstruktor - Funktionen aufrufen.
Anstatt also Feinde zu unterordnen, bei denen ein Soldat eine Pistole und ein anderer ein Gewehr und ein anderer eine Schrotflinte hat, und das ist der einzige Unterschied, können Sie einfach sagen:
Gleiches gilt für die Zerstörung. Wenn Sie spezielle Anforderungen haben (Entfernen von Ereignis-Listenern, Entfernen von Instanzen aus Arrays / Strukturen, mit denen Sie arbeiten usw.), rufen Sie diese manuell auf, damit Sie genau wissen, wann und wo in dem Programm, das gerade ausgeführt wurde.
BEARBEITEN
Wie Kryotan weiter unten ausgeführt hat, beantwortet dies das "Wie" des ursprünglichen Beitrags , macht aber kein wirklich gutes "Warum".
Wie Sie wahrscheinlich in der obigen Antwort sehen können, gibt es möglicherweise keinen großen Unterschied zwischen:
und schreiben
während nur eine größere Konstruktorfunktion haben.
Es gibt ein Argument für Objekte mit 15 oder 20 Vorbedingungen, die es einem Konstruktor sehr, sehr schwer machen würden, damit zu arbeiten, und es würde das Sehen und Erinnern erleichtern, indem diese Dinge in die Schnittstelle gezogen werden , damit Sie sehen können, wie die Instanziierung funktioniert, eine Ebene höher.
Die optionale Konfiguration von Objekten ist eine natürliche Erweiterung davon. Optional können Sie Werte auf der Schnittstelle festlegen, bevor das Objekt ausgeführt wird.
JS hat einige großartige Abkürzungen für diese Idee, die in stärker typisierten c-ähnlichen Sprachen einfach fehl am Platz zu sein scheinen.
Das heißt, wenn Sie es mit einer so langen Argumentliste in Ihrem Konstruktor zu tun haben, besteht die Möglichkeit, dass Ihr Objekt zu groß ist und so viel tut, wie es ist. Auch dies ist eine persönliche Präferenzsache, und es gibt weit und breit Ausnahmen. Wenn Sie jedoch 20 Dinge an ein Objekt übergeben, stehen die Chancen gut, dass Sie einen Weg finden, dieses Objekt weniger zu machen, indem Sie kleinere Objekte erstellen .
Ein relevanterer und allgemein anwendbarer Grund wäre, dass die Initialisierung eines Objekts auf asynchronen Daten beruht, über die Sie derzeit nicht verfügen.
Sie wissen, dass Sie das Objekt benötigen, also werden Sie es trotzdem erstellen, aber damit es ordnungsgemäß funktioniert, benötigt es Daten vom Server oder von einer anderen Datei, die es jetzt laden muss.
Auch hier ist es für das Konzept nicht wirklich wichtig, ob Sie die benötigten Daten an einen gigantischen Init übergeben oder eine Schnittstelle aufbauen, sondern für die Schnittstelle Ihres Objekts und das Design Ihres Systems ...
In Bezug auf das Erstellen des Objekts können Sie jedoch Folgendes tun:
async_loader
Möglicherweise wird ein Dateiname oder ein Ressourcenname oder was auch immer übergeben. Laden Sie diese Ressource - möglicherweise werden Audiodateien oder Bilddaten geladen, oder es werden gespeicherte Zeichenstatistiken geladen ...... und dann würde es diese Daten zurückspeisen
obj_w_async_dependencies.init(result);
.Diese Art von Dynamik findet man häufig in Web-Apps.
Nicht unbedingt in der Konstruktion eines Objekts, für Anwendungen auf höherer Ebene: Beispielsweise können Galerien sofort geladen und initialisiert werden und dann Fotos anzeigen, während sie einströmen - das ist eigentlich keine asynchrone Initialisierung, aber dort, wo sie häufiger zu sehen ist in JavaScript-Bibliotheken.
Ein Modul hängt möglicherweise von einem anderen ab, sodass die Initialisierung dieses Moduls möglicherweise verschoben wird, bis das Laden der abhängigen Elemente abgeschlossen ist.
Betrachten Sie in Bezug auf spielspezifische Instanzen eine tatsächliche
Game
Klasse.Warum können wir nicht
.start
oder.run
im Konstruktor aufrufen ?Ressourcen müssen geladen werden - der Rest von allem wurde so ziemlich definiert und ist in Ordnung, aber wenn wir versuchen, das Spiel ohne Datenbankverbindung oder ohne Texturen oder Modelle oder Sounds oder Levels auszuführen, wird es nicht so sein ein besonders interessantes Spiel ...
... na und, dann ist der Unterschied zwischen dem, was wir von einem typischen sehen
Game
, außer dass wir seiner "Go-Ahead" -Methode einen Namen geben, der interessanter ist als.init
(oder umgekehrt, die Initialisierung noch weiter auseinander zu brechen, um das Laden zu trennen). Einrichten der geladenen Dinge und Ausführen des Programms, wenn alles eingerichtet wurde).quelle
.init
, vielleicht auch nicht, aber wahrscheinlich. Ergo, gültiger Fall.Was auch immer Sie lesen, dass Init und CleanUp besser sind, sollte Ihnen auch sagen, warum. Artikel, die ihre Behauptungen nicht rechtfertigen, sind nicht lesenswert.
Durch separate Initialisierungs- und Herunterfahrfunktionen können Systeme einfacher eingerichtet und zerstört werden, da Sie auswählen können, in welcher Reihenfolge sie aufgerufen werden sollen, während Konstruktoren genau dann aufgerufen werden, wenn das Objekt erstellt wird, und Destruktoren aufgerufen werden, wenn das Objekt zerstört wird. Wenn Sie komplexe Abhängigkeiten zwischen zwei Objekten haben, müssen diese häufig vorhanden sein, bevor sie sich selbst einrichten. Dies ist jedoch häufig ein Zeichen für ein schlechtes Design an anderer Stelle.
Einige Sprachen haben keine Destruktoren, auf die Sie sich verlassen können, da Referenzzählung und Speicherbereinigung es schwieriger machen zu wissen, wann das Objekt zerstört wird. In diesen Sprachen benötigen Sie fast immer eine Methode zum Herunterfahren / Bereinigen, und einige möchten die Init-Methode für die Symmetrie hinzufügen.
quelle
Ich denke, der beste Grund ist: Pooling zuzulassen.
Wenn Sie Init und CleanUp haben, können Sie, wenn ein Objekt getötet wird, einfach CleanUp aufrufen und das Objekt auf einen Objektstapel des gleichen Typs schieben: einen 'Pool'.
Wenn Sie dann ein neues Objekt benötigen, können Sie ein Objekt aus dem Pool entfernen ODER wenn der Pool leer ist - zu schlecht - müssen Sie ein neues erstellen. Dann rufen Sie Init für dieses Objekt auf.
Eine gute Strategie besteht darin, den Pool vorab zu füllen, bevor das Spiel mit einer „guten“ Anzahl von Objekten beginnt, sodass Sie während des Spiels niemals ein gepooltes Objekt erstellen müssen.
Wenn Sie andererseits 'new' verwenden und einfach aufhören, auf ein Objekt zu verweisen, wenn es für Sie nicht von Nutzen ist, erstellen Sie Müll, der irgendwann wieder gesammelt werden muss. Diese Erinnerung ist besonders schlecht für Single-Threaded-Sprachen wie Javascript, bei denen der Garbage Collector den gesamten Code stoppt, wenn er auswertet, dass er den Speicher der nicht mehr verwendeten Objekte abrufen muss. Das Spiel hängt einige Millisekunden und das Spielerlebnis wird beeinträchtigt.
- Sie haben bereits verstanden -: Wenn Sie alle Ihre Objekte zusammenfassen, erfolgt keine Erinnerung, daher keine zufällige Verlangsamung mehr.
Es ist auch viel schneller, init für ein Objekt aus dem Pool aufzurufen, als Speicher zuzuweisen + ein neues Objekt zu initiieren.
Die Geschwindigkeitsverbesserung ist jedoch weniger wichtig, da die Objekterstellung häufig kein Leistungsengpass darstellt ... Mit wenigen Ausnahmen wie hektischen Spielen, Partikel-Engines oder physikalischen Engines, die intensiv 2D / 3D-Vektoren für ihre Berechnungen verwenden. Hier werden sowohl die Geschwindigkeit als auch die Speicherbereinigung durch die Verwendung eines Pools erheblich verbessert.
Rq: Möglicherweise benötigen Sie keine CleanUp-Methode für Ihre gepoolten Objekte, wenn Init () alles zurücksetzt.
Bearbeiten: Die Beantwortung dieses Beitrags hat mich motiviert, einen kleinen Artikel über das Pooling in Javascript fertigzustellen .
Sie finden es hier, wenn Sie interessiert sind:
http://gamealchemist.wordpress.com/
quelle
Ihre Frage ist umgekehrt ... Historisch gesehen ist die sachdienlichere Frage:
Warum ist + Bau intialisation verschmelzt , also warum nicht wir tun separat diese Schritte? Sicher geht das gegen SoC ?
Für C ++ besteht die Absicht von RAII darin, die Ressourcenerfassung und -freigabe direkt an die Objektlebensdauer zu binden, in der Hoffnung, dass dies die Ressourcenfreigabe gewährleistet. Macht es? Teilweise. Es wird zu 100% im Kontext von stapelbasierten / automatischen Variablen erfüllt, wobei das Verlassen des zugehörigen Bereichs automatisch Destruktoren aufruft / diese Variablen freigibt (daher das Qualifikationsmerkmal
automatic
). Bei Heap-Variablen bricht dieses sehr nützliche Muster jedoch leider zusammen, da Sie immer noch gezwungen sind, explizit aufzurufendelete
, um den Destruktor auszuführen. Wenn Sie dies vergessen, werden Sie immer noch von dem gebissen, was RAII zu lösen versucht. Im Kontext von Heap-zugewiesenen Variablen bietet C ++ einen begrenzten Nutzen gegenüber C (delete
vs.free()
) während die Konstruktion mit der Initialisierung in Verbindung gebracht wird, was sich negativ auf Folgendes auswirkt:Das Erstellen eines Objektsystems für Spiele / Simulationen in C wird dringend empfohlen, da es durch ein tieferes Verständnis der Annahmen, die C ++ und spätere klassische OO-Sprachen treffen, viel Licht auf die Einschränkungen von RAII und anderen solchen OO-zentrierten Mustern werfen wird (Denken Sie daran, dass C ++ als in C integriertes OO-System begann.)
quelle