Disons que mon jeu a un monstre qui peut exploser kamikaze sur le joueur. Choisissons un nom pour ce monstre au hasard: un Creeper. Ainsi, la Creeper
classe a une méthode qui ressemble à ceci:
void Creeper::kamikaze() {
EventSystem::postEvent(ENTITY_DEATH, this);
Explosion* e = new Explosion;
e->setLocation(this->location());
this->world->addEntity(e);
}
Les événements ne sont pas mis en file d'attente, ils sont envoyés immédiatement. Cela provoque la Creeper
suppression de l' objet quelque part dans l'appel à postEvent
. Quelque chose comme ça:
void World::handleEvent(int type, void* context) {
if(type == ENTITY_DEATH){
Entity* ent = dynamic_cast<Entity*>(context);
removeEntity(ent);
delete ent;
}
}
Étant donné que l' Creeper
objet est supprimé pendant que la kamikaze
méthode est toujours en cours d'exécution, il se bloque lorsqu'il essaie d'accéder this->location()
.
Une solution consiste à mettre les événements en file d'attente dans un tampon et à les envoyer ultérieurement. Est-ce la solution courante dans les jeux C ++? Cela ressemble à un peu de piratage, mais cela pourrait être dû à mon expérience avec d'autres langues avec différentes pratiques de gestion de la mémoire.
En C ++, existe-t-il une meilleure solution générale à ce problème lorsqu'un objet se supprime accidentellement de l'intérieur de l'une de ses méthodes?
la source
autorelease
Objective-C, où les suppressions sont suspendues jusqu'à "juste un peu".Réponses:
Ne supprimez pas
this
Même implicitement.
- Déjà -
La suppression d'un objet alors que l'une de ses fonctions membres est toujours sur la pile demande des ennuis. Toute architecture de code qui se traduit par ce qui se passe ("accidentellement" ou non) est objectivement mauvaise , dangereuse et doit être refactorisée immédiatement . Dans ce cas, si votre monstre va être autorisé à appeler 'World :: handleEvent', ne supprimez en aucun cas le monstre à l'intérieur de cette fonction!
(Dans cette situation spécifique, mon approche habituelle consiste à faire en sorte que le monstre mette un drapeau `` mort '' sur lui-même, et que l'objet Monde - ou quelque chose comme ça - teste ce drapeau `` mort '' une fois par image, en supprimant ces objets de sa liste d'objets dans le monde, et soit en le supprimant, soit en le retournant dans un pool de monstres ou tout ce qui est approprié. À ce moment, le monde envoie également des notifications sur la suppression, afin que d'autres objets dans le monde sachent que le monstre a a cessé d'exister et peut y déposer tous les pointeurs qu'ils pourraient contenir. Le monde le fait à un moment sûr, lorsqu'il sait qu'aucun objet n'est en cours de traitement, vous n'avez donc pas à vous soucier de la pile se déroulant en un point où le pointeur «ceci» pointe vers la mémoire libérée.)
la source
Au lieu de mettre les événements en file d'attente dans un tampon, placez les suppressions dans un tampon. La suppression différée a le potentiel de simplifier massivement la logique; vous pouvez réellement libérer de la mémoire à la fin ou au début d'une image lorsque vous savez qu'il ne se passe rien d'intéressant à vos objets et les supprimer de n'importe où.
la source
NSAutoreleasePool
objectif d'Objectif-C serait bien dans cette situation. Pourrait avoir à faire unDeletionPool
avec des modèles C ++ ou quelque chose.Au lieu de laisser le monde gérer la suppression, vous pouvez laisser l'instance d'une autre classe servir de compartiment pour stocker toutes les entités supprimées. Cette instance particulière doit écouter les
ENTITY_DEATH
événements et les gérer de telle sorte qu'ils les mettent en file d'attente. LeWorld
peut ensuite parcourir ces instances et effectuer des opérations post-mort après le rendu de la trame et «effacer» ce compartiment, qui à son tour effectuerait la suppression réelle des instances d'entités.Un exemple d'une telle classe serait comme ceci: http://ideone.com/7Upza
la source
World
classe.Je suggérerais d'implémenter une fabrique utilisée pour toutes les allocations d'objets de jeu dans le jeu. Ainsi, au lieu d'appeler nouveau vous-même, vous diriez à l'usine de créer quelque chose pour vous.
Par exemple
Chaque fois que vous souhaitez supprimer un objet, la fabrique pousse l'objet dans un tampon qui est effacé la trame suivante. La destruction retardée est très importante dans la plupart des scénarios.
Pensez également à envoyer tous les messages avec un retard d'une trame. Il n'y a que quelques exceptions où vous devez envoyer immédiatement, la grande majorité des cas cependant juste
la source
Vous pouvez implémenter vous-même la mémoire gérée en C ++, de sorte que lorsqu'il
ENTITY_DEATH
est appelé, tout ce qui se passe est que le nombre de ses références soit réduit de un.Plus tard, comme @John l'a suggéré au début de chaque trame, vous pouvez vérifier quelles entités sont inutiles (celles avec zéro référence) et les supprimer. Par exemple, vous pouvez utiliser
boost::shared_ptr<T>
( documenté ici ) ou si vous utilisez C ++ 11 (VC2010)std::tr1::shared_ptr<T>
la source
std::shared_ptr<T>
, pas des rapports techniques! - Vous devrez spécifier un suppresseur personnalisé, sinon il supprimera également l'objet immédiatement lorsque le nombre de références atteint zéro.Utilisez le regroupement et ne supprimez pas réellement les objets. Au lieu de cela, modifiez la structure de données dans laquelle ils sont enregistrés. Par exemple, pour le rendu des objets, il existe un objet de scène et toutes les entités qui y sont en quelque sorte enregistrées pour le rendu, la détection de collision, etc. Au lieu de supprimer l'objet, détachez-le de la scène et insérez-le dans un pool d'objets morts. Cette méthode permettra non seulement d'éviter les problèmes de mémoire (comme un objet qui se supprime), mais peut également accélérer votre jeu si vous utilisez correctement le pool.
la source
Ce que nous avons fait dans un jeu était d'utiliser le placement nouveau
le eventPool n'était qu'un grand tableau de mémoire qui a été découpé, et le pointeur de chaque segment a été stocké. Donc alloc () retournerait l'adresse d'un bloc de mémoire libre. Dans notre eventPool, la mémoire était traitée comme une pile, donc après que tous les événements aient été envoyés, nous venions de réinitialiser le pointeur de pile au début du tableau.
En raison de la façon dont notre système d'événements fonctionnait, nous n'avions pas besoin d'appeler les destructeurs des evetns. Ainsi, le pool enregistrerait simplement le bloc de mémoire comme libre et l'allouerait.
Cela nous a donné une vitesse énorme.
Aussi ... Nous avons en fait utilisé des pools de mémoire pour tous les objets alloués dynamiquement en développement, car c'était un excellent moyen de trouver des fuites de mémoire, s'il restait des objets dans les pools lorsque le jeu se terminait (normalement), il était probable une fuite de mem.
la source