[C++] Returning to method after class is deleted?

Started by
7 comments, last by Ravyne 16 years, 8 months ago
I have an interesting issue that I've come across in my game architecture. Let me explain. My game engine relies on a stack of GameState objects which represent various portions of the game - A splash screen, the main menu, pause screen, gameplay - I've also seen similar constructs refered to as "scenes" - in the XNA SpaceWar starter kit for example. The stack, of course, behaves like a stack. When you enter a new GameState, it is instantiated and pushed onto the stack; when you leave a GameState, it is popped off the stack and deleted. When the game executes an update cycle, it calls the update method of all active GameState objects on the stack, from oldest to newest. It is the common case that a GameState object will call onto the stack object in order to pop itself when that state has ended. In turn, the stack object decrements its Top-of-Stack pointer and deletes the previous TOS object (The GameState which called pop.) Here is where it gets interesting because, when the stack's pop method completes, the program jumps back to the caller, which, in this case, is the update method of the just-deleted GameState object. As far as I know, the code of a given class lives for the lifetime of the application (or DLL, I suppose) so my get tells me that this should be OK -- with the caveat that the data of the deleted GameState object should be considered invalid and neither read from nor written to after the pop. However, this is definitely one area of C++ that I would love for someone to confirm or refute my gut instinct. Does the C++ standard make any guarantees as to whether this is safe or not? Is behavior in this situation undefined? I have been using this architecture for a few months on two projects and it has never caused any problems, but "it works" is no guarantee that its correct, as we all know. It is not out of the question to implement deferred deletion on GameState objects (In fact it would be trivial and might even be worthwhile on the possibility that I might someday have a reason to touch the class's data after it is popped.) but is it necessary now to avoid undefined behavior or to respect the standard?

throw table_exception("(? ???)? ? ???");

Advertisement
In my quick glance, the standard makes no reference at all to anything regarding that; not to even declare it undefined behavior.

In my (relatively) limited experience, being in a member function of a deleted (or never existing in the first place) object is no different than a normal function with a bad pointer. The pointer just happens to be this in the scenario.
So what you doing is in an object you are calling a function that deletes the object that its inside of? And your wondering if thats and issue since your deleting the function your currently using too?
As long as you make sure not to access any of the object's data members, or call any virtual functions once its deleted, it should be completely safe
ByteMe95::~ByteMe95()My S(h)ite
I think I've read that "delete this;" can be perfectly valid, well defined code (not sure how good it is as a design). The this pointer becomes invalid and anything that implicitly uses the this pointer, like accessing non-static members, should be avoided. The code and stack exist outside of the instance so nothing really gets corrupted by doing this.

Still it might be a good idea to have the GameState indicate what to do through a return value from the update function so there's less room for confusion.
Quote:Original post by LittleFreak
So what you doing is in an object you are calling a function that deletes the object that its inside of? And your wondering if thats and issue since your deleting the function your currently using too?


Yes basically, except you can't delete a function. When you delete an instance of a class you are only effectively deleting its data.


Quote:Original post by Vorpy
Still it might be a good idea to have the GameState indicate what to do through a return value from the update function so there's less room for confusion


Yes, possibly. I can't do it that way specifically because there are more involved scenarios that already exist such as popping two states and other worse-yet scenarios such as popping multiple states then pushing one or more, all in that update function. If I go down that route I'll probably make a command-queue of sorts that executes before the next round of updates.


Everything that's been said seems to confirm my instinct, and logically it all makes sense, so I think I'm going to declare it good to go unless any one has any objections.

throw table_exception("(? ???)? ? ???");

It would be better if the state object set a flag saying that it is done, and the code managing the stack popped the state off the stack. The problem here is that you are creating a stale pointer, and either you have to forever monitor the code to ensure that the stale pointer is never referenced or you have to write code that checks the pointer whenever it is referenced.

Now, if this is your own personal stuff or it is a small project then it's not a big deal; however, it would be big a problem on a large project.
John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!
Quote:Original post by Vorpy
I think I've read that "delete this;" can be perfectly valid, well defined code (not sure how good it is as a design).


Yes, it's valid. To the same degree that you can run safely with scissors.

Quote:I've also seen similar constructs refered to as "scenes" - in the XNA SpaceWar starter kit for example.


XNA is C#. Memory management there has nothing to do with C++, even if syntax looks similar.

Quote:However, this is definitely one area of C++ that I would love for someone to confirm or refute my gut instinct. Does the C++ standard make any guarantees as to whether this is safe or not? Is behavior in this situation undefined?


My gut tells me there's something wrong with design. It's not really about whether it's legit or even guaranteed. If you find yourself within a deleted object by a legal code-path, there's something wrong with regards to class responsibilities.

You're basically establishing a responsibility between certain set of objects (caller, state, stack and method) to behave in exactly specific way. This will be your source of problems.


A better solution is to let the called method determined whether to pop or not.
class GameState{  bool update()  {    ...    // iterate through objects, gs being current    if (gs.update() ) {      stack.pop(gs); // or whatever      delete gs;    }  }}


Give the caller the responsibility for popping and cleaning up. Mostly, because you really want all your allocations to be in in the same place where allocations are made.
Quote:Original post by Antheus
Quote:I've also seen similar constructs refered to as "scenes" - in the XNA SpaceWar starter kit for example.


XNA is C#. Memory management there has nothing to do with C++, even if syntax looks similar.


To be clear, I'm not comparing my implementation to the Starter Kits. The only point I was trying to make is that what I call a "GameState" I've also seen called a "Scene" -- which is what the XNA starter kit calls it. I mentioned it only to indicate that the "GameState" and "scene" are the same concept, to provide a frame of reference.

Quote:Original post by Antheus
Quote:However, this is definitely one area of C++ that I would love for someone to confirm or refute my gut instinct. Does the C++ standard make any guarantees as to whether this is safe or not? Is behavior in this situation undefined?


My gut tells me there's something wrong with design. It's not really about whether it's legit or even guaranteed. If you find yourself within a deleted object by a legal code-path, there's something wrong with regards to class responsibilities.

You're basically establishing a responsibility between certain set of objects (caller, state, stack and method) to behave in exactly specific way. This will be your source of problems.


A better solution is to let the called method determined whether to pop or not.
*** Source Snippet Removed ***

Give the caller the responsibility for popping and cleaning up. Mostly, because you really want all your allocations to be in in the same place where allocations are made.


Quote:Original post by JohnBolton
It would be better if the state object set a flag saying that it is done, and the code managing the stack popped the state off the stack. The problem here is that you are creating a stale pointer, and either you have to forever monitor the code to ensure that the stale pointer is never referenced or you have to write code that checks the pointer whenever it is referenced.


I'll discuss these last two together since I think that you're largely suggesting the same thing.

I think I would probably be fine, however there is probably too much potential for misuse leading to strange bugs. The scope of the stale pointer is very small, but the greater issue seems to be the dependance and overall touchyness of the situation.

So I will probably have to implement the command queue idea I spoke of. It seems to be the only way to move the actual deletion outside the update call that still allows more complex scenarios involving some combination of pushes/pops while preserving order of commands.

[Edited by - ravyne2001 on August 14, 2007 3:55:40 AM]

throw table_exception("(? ???)? ? ???");

This topic is closed to new replies.

Advertisement