Angenommen, in meinem Spiel befindet sich ein Monster, das Kamikaze auf dem Spieler explodieren lassen kann. Lass uns zufällig einen Namen für dieses Monster auswählen: einen Creeper. Die Creeper
Klasse hat also eine Methode, die ungefähr so aussieht:
void Creeper::kamikaze() {
EventSystem::postEvent(ENTITY_DEATH, this);
Explosion* e = new Explosion;
e->setLocation(this->location());
this->world->addEntity(e);
}
Die Ereignisse werden nicht in die Warteschlange gestellt, sondern sofort versendet. Dies bewirkt, dass das Creeper
Objekt irgendwo innerhalb des Aufrufs von gelöscht wird postEvent
. Etwas wie das:
void World::handleEvent(int type, void* context) {
if(type == ENTITY_DEATH){
Entity* ent = dynamic_cast<Entity*>(context);
removeEntity(ent);
delete ent;
}
}
Da das Creeper
Objekt gelöscht wird, während die kamikaze
Methode noch ausgeführt wird, stürzt es beim Zugriff ab this->location()
.
Eine Lösung besteht darin, die Ereignisse in einen Puffer zu stellen und sie später zu versenden. Ist das die übliche Lösung in C ++ - Spielen? Es fühlt sich wie ein Hack an, aber das könnte nur an meiner Erfahrung mit anderen Sprachen mit anderen Speicherverwaltungsmethoden liegen.
Gibt es in C ++ eine bessere allgemeine Lösung für dieses Problem, bei der sich ein Objekt versehentlich aus einer seiner Methoden löscht?
quelle
autorelease
in Objective-C werfen , wo Löschungen "nur für ein bisschen" zurückgehalten werden.Antworten:
Nicht löschen
this
Selbst implizit.
- Je -
Das Löschen eines Objekts, während sich eine seiner Mitgliedsfunktionen noch auf dem Stapel befindet, ist problematisch. Jede Codearchitektur, die dazu führt ("versehentlich" oder nicht), ist objektiv schlecht , gefährlich und sollte sofort überarbeitet werden . In diesem Fall, wenn dein Monster 'World :: handleEvent' aufrufen darf , lösche auf keinen Fall das Monster in dieser Funktion!
(In dieser speziellen Situation ist es meine übliche Vorgehensweise, das Monster eine "tote" Flagge auf sich selbst setzen zu lassen und das Weltobjekt - oder so ähnlich - einmal pro Frame auf diese "tote" Flagge testen zu lassen und diese Objekte zu entfernen Zu diesem Zeitpunkt sendet die Welt auch Benachrichtigungen über das Löschen, damit andere Objekte auf der Welt wissen, dass das Monster es hat Die Welt tut dies zu einem sicheren Zeitpunkt, wenn sie weiß, dass derzeit keine Objekte verarbeitet werden, sodass Sie sich keine Sorgen mehr machen müssen, dass der Stapel zu einem bestimmten Zeitpunkt abgewickelt wird wo der 'this' Zeiger auf den freigegebenen Speicher zeigt.)
quelle
Anstatt Ereignisse in einem Puffer in die Warteschlange zu stellen, müssen Sie Löschvorgänge in einem Puffer in die Warteschlange stellen . Delayed-Deletion hat das Potenzial, die Logik massiv zu vereinfachen. Sie können tatsächlich Speicher am Ende oder Anfang eines Frames freigeben, wenn Sie wissen, dass Ihren Objekten nichts Interessantes passiert, und von absolut jedem Ort aus löschen.
quelle
NSAutoreleasePool
von Objective-C in dieser Situation wäre. Möglicherweise müssen Sie eineDeletionPool
mit C ++ - Vorlagen oder so etwas machen.Anstatt die Welt mit dem Löschen befassen zu müssen, können Sie die Instanz einer anderen Klasse als Bucket zum Speichern aller gelöschten Entitäten verwenden. Diese bestimmte Instanz sollte
ENTITY_DEATH
Ereignisse abhören und sie so behandeln, dass sie in eine Warteschlange eingereiht werden. DasWorld
kann dann über diese Instanzen iterieren und Operationen nach dem Tod ausführen, nachdem der Frame gerendert wurde, und diesen Bucket "löschen", was wiederum das tatsächliche Löschen von Instanzen von Entitäten durchführen würde.Ein Beispiel für eine solche Klasse wäre: http://ideone.com/7Upza
quelle
World
Klasse eine lebendige Liste und eine tote Liste von Entitäten .Ich würde vorschlagen, eine Factory zu implementieren, die für alle Zuordnungen von Spielobjekten im Spiel verwendet wird. Anstatt selbst neu anzurufen, würden Sie die Fabrik bitten, etwas für Sie zu kreieren.
Beispielsweise
Wann immer Sie ein Objekt löschen möchten, schiebt die Factory das Objekt in einen Puffer, der im nächsten Frame gelöscht wird. Eine verzögerte Zerstörung ist in den meisten Szenarien sehr wichtig.
Denken Sie auch daran, alle Nachrichten mit einer Verzögerung von einem Frame zu senden. Es gibt nur wenige Ausnahmen, bei denen Sie sofort senden müssen, die überwiegende Mehrheit der Fälle jedoch nur
quelle
Sie können Managed Memory in C ++ selbst implementieren, sodass beim
ENTITY_DEATH
Aufruf lediglich die Anzahl der Referenzen um eins reduziert wird.Später, wie @John am Anfang jedes Frames vorgeschlagen hat, können Sie überprüfen, welche Entities nutzlos sind (diejenigen mit Nullreferenzen) und sie löschen. Zum Beispiel können Sie verwenden
boost::shared_ptr<T>
( hier dokumentiert ) oder wenn Sie C ++ 11 (VC2010) verwendenstd::tr1::shared_ptr<T>
quelle
std::shared_ptr<T>
keine technischen Berichte! - Sie müssen einen benutzerdefinierten Löscher angeben. Andernfalls wird das Objekt sofort gelöscht, wenn der Referenzzähler Null erreicht.Verwenden Sie Pooling und löschen Sie keine Objekte. Ändern Sie stattdessen die Datenstruktur, in der sie registriert sind. Zum Beispiel für das Rendern von Objekten gibt es ein Szenenobjekt und alle Entitäten, die irgendwie für das Rendern, die Kollisionserkennung usw. registriert sind. Anstatt das Objekt zu löschen, trennen Sie es von der Szene und fügen es in einen Pool toter Objekte ein. Diese Methode verhindert nicht nur Speicherprobleme (wie das Löschen eines Objekts selbst), sondern beschleunigt möglicherweise auch Ihr Spiel, wenn Sie den Pool ordnungsgemäß verwenden.
quelle
Was wir in einem Spiel gemacht haben, war die Verwendung der neuen Platzierung
Der eventPool war nur ein großes Array von Speicher, der aufgeschlüsselt wurde, und der Zeiger auf jedes Segment wurde gespeichert. Somit würde alloc () die Adresse eines freien Speicherblocks zurückgeben. In unserem eventPool wurde der Speicher als Stapel behandelt. Nachdem alle Ereignisse gesendet wurden, haben wir den Stapelzeiger einfach auf den Anfang des Arrays zurückgesetzt.
Aufgrund der Funktionsweise unseres Ereignissystems mussten wir die Destruktoren nicht am Abend aufrufen. Der Pool würde also einfach den Speicherblock als frei registrieren und ihn zuweisen.
Dies gab uns eine enorme Geschwindigkeit.
Außerdem ... haben wir tatsächlich Speicherpools für alle dynamisch zugewiesenen Objekte in der Entwicklung verwendet, da dies eine großartige Möglichkeit war, Speicherlecks zu finden. Wenn sich beim Beenden des Spiels (normalerweise) noch Objekte in den Pools befanden, war dies wahrscheinlich der Fall ein mem leck.
quelle