20
\$\begingroup\$

Let's say my game has a monster that can kamikaze explode on the player. Let's pick a name for this monster at random: a Creeper. So, the Creeper class has a method that looks something like this:

void Creeper::kamikaze() {
    EventSystem::postEvent(ENTITY_DEATH, this);

    Explosion* e = new Explosion;
    e->setLocation(this->location());
    this->world->addEntity(e);
}

The events are not queued, they get dispatched immediately. This causes the Creeper object to get deleted somewhere inside the call to postEvent. Something like this:

void World::handleEvent(int type, void* context) {
    if(type == ENTITY_DEATH){
        Entity* ent = dynamic_cast<Entity*>(context);
        removeEntity(ent);
        delete ent;
    }
}

Because the Creeper object gets deleted while the kamikaze method is still running, it will crash when it tries to access this->location().

One solution is to queue the events into a buffer and dispatch them later. Is that the common solution in C++ games? It feels like a bit of a hack, but that might just be because of my experience with other languages with different memory management practices.

In C++, is there a better general solution to this problem where an object accidentally deletes itself from inside one of its methods?

\$\endgroup\$
3
  • 6
    \$\begingroup\$ uh, how about you call postEvent at the END of the kamikaze method instead of at the start? \$\endgroup\$
    – Hackworth
    Commented Apr 6, 2012 at 5:02
  • \$\begingroup\$ @Hackworth that would work for this specific example, but I'm looking for a more general solution. I want to be able to post events from anywhere and not be afraid of causing crashes. \$\endgroup\$ Commented Apr 6, 2012 at 5:06
  • \$\begingroup\$ You could also take a look at the implementation of autorelease in Objective-C, where deletions are held off till for "just a bit". \$\endgroup\$ Commented Apr 7, 2012 at 14:17

7 Answers 7

39
\$\begingroup\$

Don't delete this

Even implicitly.

- Ever -

Deleting an object while one of its member functions is still on the stack is begging for trouble. Any code architecture which results in that happening ("accidentally" or not) is objectively bad, is dangerous, and should be refactored immediately. In this case, if your monster is going to be allowed to call 'World::handleEvent', do not, under any circumstances, delete the monster inside that function!

(In this specific situation, my usual approach is to have the monster set a 'dead' flag on itself, and have the World object -- or something like it -- test for that 'dead' flag once per frame, removing those objects from its list of objects in the world, and either deleting it or returning it to a monster pool or whatever is appropriate. At this time, the world also sends out notifications about the deletion, so other objects in the world know that the monster has stopped existing, and can drop any pointers to it that they might be holding. The world does this at a safe time, when it knows that no objects are currently processing, so you don't have to worry about the stack unwinding to a point where the 'this' pointer points at freed memory.)

\$\endgroup\$
19
  • 6
    \$\begingroup\$ The second half of your answer in parenthesis was helpful. Polling for a flag is a good solution. But, I was asking how to avoid doing it, not wether it was a good or bad thing to do. If a question asks "How can I avoid doing X accidentally?", and your answer is "Never do X ever, even accidentally" in bold, that doesn't actually answer the question. \$\endgroup\$ Commented Apr 6, 2012 at 6:49
  • 7
    \$\begingroup\$ I stand behind the comments I made in the first half of my answer, and I do feel that they fully answer the question as originally phrased. The important point that I'll repeat here is that an object does not delete itself. Ever. It doesn't call someone else to delete it. Ever. Instead, you need to have something else, outside the object, which owns the object and is responsible for noticing when the object needs to be destroyed. This isn't a "just when a monster dies" thing; this is for all C++ code always, everywhere, for all time. No exceptions. \$\endgroup\$ Commented Apr 6, 2012 at 8:09
  • 3
    \$\begingroup\$ @TrevorPowell I'm not saying that you are wrong. In fact, I agree with you. I'm just saying it doesn't actually answer the question that was asked. It's like if you asked me "How do I get audio into my game?" and my answer was "I can't believe you don't have audio. Put audio in your game right now." Then in parenthesis down the bottom I put "(You can use FMOD)," which is an actual answer. \$\endgroup\$ Commented Apr 6, 2012 at 8:31
  • 6
    \$\begingroup\$ @TrevorPowell This is where you are wrong. It's not "just discipline" if I don't know any alternatives. The example code I gave is purely theoretical. I already know it's a bad design, but my C++ is rusty, so I thought I would ask about better designs here before I actually code what I want. So I came to ask about alternative designs. "Add a deletion flag" is an alternative. "Never do it" is not an alternative. It's just telling me what I already know. It feels as though you wrote the answer without reading the question properly. \$\endgroup\$ Commented Apr 6, 2012 at 9:10
  • 4
    \$\begingroup\$ @Bobby The question is "How to NOT do X". Simply saying "Do NOT do X" is a worthless answer. IF the question had been "I've been doing X" or "I was thinking of doing X" or any variant of that then it would meet the parameters of the meta discussion, but not in it's present form. \$\endgroup\$ Commented Apr 6, 2012 at 13:35
21
\$\begingroup\$

Instead of queuing events in a buffer, queue up deletions in a buffer. Delayed-deletion has the potential to massively simplify the logic; you can actually free up memory at the end or beginning of a frame when you know nothing interesting is happening to your objects, and delete from absolutely anywhere.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Funny you mention this, because I was thinking how nice an NSAutoreleasePool from Objective-C would be in this situation. Might have to make a DeletionPool with C++ templates or something. \$\endgroup\$ Commented Apr 6, 2012 at 5:08
  • \$\begingroup\$ @TomDalling The one thing to be careful about if you make the buffer external to the object is that an object could want to be deleted for multiple reasons on a single frame, and it'd be possible to try to delete it several times. \$\endgroup\$ Commented Apr 6, 2012 at 5:28
  • \$\begingroup\$ Very true. I will have to keep the pointers in a std::set. \$\endgroup\$ Commented Apr 6, 2012 at 5:43
  • 5
    \$\begingroup\$ Rather than a buffer of objects to delete, you can also just set a flag in the object. Once you start realizing how much you want to avoid calling new or delete during runtime and move to an object pool, that'll be both simpler and faster. \$\endgroup\$ Commented Apr 6, 2012 at 6:06
4
\$\begingroup\$

Instead of letting the world to handle the deletion you could let another class's instance to serve as a bucket to store all deleted entities. This particular instance should listen to ENTITY_DEATH events and handle them such that it queues them up. The World can then iterate over these instances and perform post-death operations after the frame has been rendered and 'clear' this bucket, which in turn would perform the actual deletion of entities instances.

An example of such a class would be like this: http://ideone.com/7Upza

\$\endgroup\$
1
  • \$\begingroup\$ +1, that's an alternative to flagging the entities directly. More directly, just have an alive-list and a dead-list of entities directly in the World class. \$\endgroup\$ Commented Apr 6, 2012 at 11:30
2
\$\begingroup\$

I would suggest implementing a factory that's used for all game object allocations in the game. So instead of calling new yourself, you would tell the factory to create something for you.

For example

Object* o = gFactory->Create("Explosion");

Whenever you want to delete an object, the factory pushes the object in a buffer that is cleared the next frame. Delayed destruction is very important in most scenarios.

Also consider sending all messages with a delay of one frame. There is only a couple of exceptions where you need to send immediately, that vast majority of cases however just

\$\endgroup\$
2
\$\begingroup\$

You can implement managed-memory in C++ yourself, so that when ENTITY_DEATH is called, all that happens is the number of its references is reduced by one.

Later as @John suggested at the begging of every frame you can check which entities are useless (those with zero references) and delete them. For example you can use boost::shared_ptr<T> (documented here) or if you are using C++11(VC2010) std::tr1::shared_ptr<T>

\$\endgroup\$
2
  • \$\begingroup\$ Just std::shared_ptr<T>, not technical reports! — You will have to specify a custom deleter, otherwise it will also delete the object immediately when the reference count reaches zero. \$\endgroup\$ Commented Apr 6, 2012 at 10:32
  • 1
    \$\begingroup\$ @leftaroundabout it really depends, at least I needed to use tr1 in gcc. but in VC there was no need for that. \$\endgroup\$
    – Ali1S232
    Commented Apr 6, 2012 at 11:44
2
\$\begingroup\$

Use pooling and don't actually delete objects. Instead, change the data structure they are registered to. For example for rendering objects there is a scene object and all the entities somehow registered to it for rendering, collision detection etc. Instead of deleting the object, detach it from the scene and insert into a dead objects pool. This method will not only prevent memory problems (such as an object deleting itself) but also may speed up your game if you use the pool correctly.

\$\endgroup\$
1
\$\begingroup\$

What we did in a game was use placement new

SomeEvent* obj = new(eventPool.alloc()) new SomeEvent();

the eventPool was just a big array of memory which was carved up, and the pointer to each segment was stored. So alloc() would return the address of a free block of memory. In our eventPool the memory was treated as a stack, so after all the events had been sent we'd just reset the stack pointer back to the start of the array.

Because of the way our event system worked we didn't need to call the destructors on the evetns. So the pool would simply register the block of memory as free, and would allocate it.

This gave us a huge speed up.

Also ... We actually used memory pools for all dynamically allocated objected in development, as it was a great way of finding memory leaks, if there was any objects left in the pools when the game exited (normally) then it was likely there was a mem leak.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .