Today, I ended up doing something really stupid, and I don't want any of you doing what I did today. If breakpoints and debugging features are part of your IDE, please, for the love of God, please use them!

While working on a new game concept I thought of a couple of days ago, I stopped and thought to myself "I'm going to use C++ this time and not use pure C to avoid the mistake of letting my cross platform code base getting messy again now that I'm more comfortable with the various mobile and desktop APIs and what not". So I start writing my basic application classes with portability in mind. For reasons I don't feel like getting into, I made my decision to use OpenGL via GLUT on MacOSX. I know, I know, I've never recommended GLUT in the past, but this time, I felt like using it since the game only uses either a mouse or a touch screen anyway (except for the old fashioned exit procedure; the escape key).

I don't know about you, but I have a tendancy to think about how I want my app to execute! I liking having my main() function contain as little code as possible. With that in mind, I ended up writing my main.cpp file to look like this:

Okay, hold on, I promise you the problem with this code is NOT that obvious! You're probably thinking "Yeah, no s@#% sherlock! You didn't call delete or your game loop function!" Actually, I did. The class constructor calls the initialization function where glutMainLoop is called upon success. Instinctively, I check my code to verify that everything is uninitialized properly, even more than I check for successful initialization. So I put a breakpoint inside of exit_func to verify that it's being called (well, I knew the function works, but I habitually check anyway). Just in case you're wondering, the code for my class (the initialization stuff) looked like this:

And yes, the event callback functions were declared as static, so I needed an extra copy of my this pointer. Since there only needs to be one instance of this class, it doesn't really matter, IMHO.

This is where I found my REAL problem: app is still NULL! I was kinda dumbfounded when I realized that the class instance didn't get uninitialized (hence my deconstructor was never called). I spent a few minutes going over the code to see why this wasn't working. The last thing I wanted was to be leaking memory every launch. After a few more moments of scratching my head, I realized that the problem was that it the call to "new" doesn't return anyway. So the logical thing to do was to use the copy of my this pointer and delete that explicitly instead. After that thought, I added another function:

So, I added a breakpoint, ran my app in debug mode, and this time, my class instance not only gets deleted properly, but now my deconstructor gets called, and calls the uninit function. Bam, problem solved! Now my main function looks like this:

int main( int argc, char* argv[] )
{
new game_app_t;
return 0;
}

So short, so sweet. Isn't C++ great once you use it properly?

So, the moral of the story is- "Don't be as stupid as Shogun?" Yeah, that too, but please, use your breakpoints and always, ALWAYS, cover your digital butt! I've seen a handful of rookie game programmers who have never learned to use breakpoints (I was one of those). What I did was dumb. I made this mistake for you, please don't do it for yourself.

"One objection to a “critique of C#” would be that you can’t talk about C# without talking about the whole “.Net experience”. However, one can approach the topic of Hitler without a complete discussion of Nationalist Socialism, so I feel justified." - Steve White.

Beginner in Game Development? Read here.Super Mario Bros clone tutorial written in XNA 4.0 [MonoGame, ANX, and MonoXNA] by Scott Haley
If you have found any of the posts helpful, please show your appreciation by clicking the up arrow on those posts

Spoiler

How on earth can you go around telling lies, be shown that they are lies, then continue to ignore the evidence to the contrary. I'm not qualified to deal with this, sorry. - HodgmanNever crash to desktop. That's the equivalent of a Starbucks rep punching you in the face when you try to order a coffee with soymilk when they run out of soymilk. - KoobazaurFor programmers, R & D means "Research & Duplicate".Just remember... XML is like violence. If it doesn't work, you just aren't using enough of it... - MoeEvery time I go into my account settings I feel like I've never used the internet before. - booleanPerson X: Do you want to live forever?
Person Y: No, but I want to live a long life.
Person X: Only cowards live that long.
Person Y: And yet, we travel to seek the counsel of these "cowards".

Beginner in Game Development? Read here.Super Mario Bros clone tutorial written in XNA 4.0 [MonoGame, ANX, and MonoXNA] by Scott Haley
If you have found any of the posts helpful, please show your appreciation by clicking the up arrow on those posts

Spoiler

How on earth can you go around telling lies, be shown that they are lies, then continue to ignore the evidence to the contrary. I'm not qualified to deal with this, sorry. - HodgmanNever crash to desktop. That's the equivalent of a Starbucks rep punching you in the face when you try to order a coffee with soymilk when they run out of soymilk. - KoobazaurFor programmers, R & D means "Research & Duplicate".Just remember... XML is like violence. If it doesn't work, you just aren't using enough of it... - MoeEvery time I go into my account settings I feel like I've never used the internet before. - booleanPerson X: Do you want to live forever?
Person Y: No, but I want to live a long life.
Person X: Only cowards live that long.
Person Y: And yet, we travel to seek the counsel of these "cowards".

Be nice. I haven't C++'d since college. But I've been VB.net and C#'ing for years now lol

Though in all seriousness, shouldn't fail_safe be an exception in a function as opposed to a macro?

Note: I'm well aware that the OP is moving from C to C++, so idiomatic C++ is not expected from the start. But there's no harm in asking

Beginner in Game Development? Read here.Super Mario Bros clone tutorial written in XNA 4.0 [MonoGame, ANX, and MonoXNA] by Scott Haley
If you have found any of the posts helpful, please show your appreciation by clicking the up arrow on those posts

Spoiler

How on earth can you go around telling lies, be shown that they are lies, then continue to ignore the evidence to the contrary. I'm not qualified to deal with this, sorry. - HodgmanNever crash to desktop. That's the equivalent of a Starbucks rep punching you in the face when you try to order a coffee with soymilk when they run out of soymilk. - KoobazaurFor programmers, R & D means "Research & Duplicate".Just remember... XML is like violence. If it doesn't work, you just aren't using enough of it... - MoeEvery time I go into my account settings I feel like I've never used the internet before. - booleanPerson X: Do you want to live forever?
Person Y: No, but I want to live a long life.
Person X: Only cowards live that long.
Person Y: And yet, we travel to seek the counsel of these "cowards".

Well, you can't explicitly use "this" in a static function, hence why I created a copy of it. I didn't feel like changing the code structure again if I didn't have to. Second, I forgot to add the '()'.

Mr. Hodgeman, I'm a "heap freak".

Oh, and the macros were just a quick way of getting around. Second, macros can be more optimal (IMO) and for small operations I'd much rather use them to do things that require 2 lines of code than an actual short function. Even if you inline a function that you use frequently, a macro is faster because the compiler doesn't have to push the parameters, call the function, pop them off the stack, do push the registers (eax, ebx, etc.), do whatever, restore the registers and return. I know that it doesn't impact performance really, but after writing and reading so much assembly code, I tend to think about these things a bit too much.

"One objection to a “critique of C#” would be that you can’t talk about C# without talking about the whole “.Net experience”. However, one can approach the topic of Hitler without a complete discussion of Nationalist Socialism, so I feel justified." - Steve White.

I'll let skilled C++ programmers answer that. As I would just be saying stuff that may or may not be true.

Beginner in Game Development? Read here.Super Mario Bros clone tutorial written in XNA 4.0 [MonoGame, ANX, and MonoXNA] by Scott Haley
If you have found any of the posts helpful, please show your appreciation by clicking the up arrow on those posts

Spoiler

How on earth can you go around telling lies, be shown that they are lies, then continue to ignore the evidence to the contrary. I'm not qualified to deal with this, sorry. - HodgmanNever crash to desktop. That's the equivalent of a Starbucks rep punching you in the face when you try to order a coffee with soymilk when they run out of soymilk. - KoobazaurFor programmers, R & D means "Research & Duplicate".Just remember... XML is like violence. If it doesn't work, you just aren't using enough of it... - MoeEvery time I go into my account settings I feel like I've never used the internet before. - booleanPerson X: Do you want to live forever?
Person Y: No, but I want to live a long life.
Person X: Only cowards live that long.
Person Y: And yet, we travel to seek the counsel of these "cowards".

Inline functions are preferred over macros in most C++ style guides, and should only be used where absolutely necessary.

In the case of safe_delete, an inline function is just as applicable, which means a macro isn't necessary.

template<class T> void safe_delete(T* x) { delete x; x = NULL; }

BTW, the if isn't necessary, as delete NULL; is valid (it does nothing) -- the if statement is often put there by programmers who learned on MSVC 6.0, which implemented "Microsoft C++", rather than C++98.

The main evilness about macros is that they don't obey C++ name scoping rules, so they can inadvertantly break other code.

e.g. say that I buy your code as a bit of middleware, and #include it into my own code that follows:

"One objection to a “critique of C#” would be that you can’t talk about C# without talking about the whole “.Net experience”. However, one can approach the topic of Hitler without a complete discussion of Nationalist Socialism, so I feel justified." - Steve White.

Sure, except for you're not using it properly (to put it bluntly). Seeing as the constructor never finishes being called, and the non-trivial destructor is called (while effectively in the constructor), you've officially invoked undefined behavior. See this StackOverflow question for some details

Sure, except for you're not using it properly (to put it bluntly). Seeing as the constructor never finishes being called, and the non-trivial destructor is called (while effectively in the constructor), you've officially invoked undefined behavior. See this StackOverflow question for some details

"One objection to a “critique of C#” would be that you can’t talk about C# without talking about the whole “.Net experience”. However, one can approach the topic of Hitler without a complete discussion of Nationalist Socialism, so I feel justified." - Steve White.

Nope. First, you can't delete things on the stack. And second, even if you manually called the destructor, it's still undefined behavior. If you use the non-trivial destructor in any way before returning from the constructor, it's undefined behavior.

Wow, are you psychic or something? How did you know I started with MSVC 6.0? Heh, I guess when you're experienced enough, it's just obvious.

Hahahah, I didn't actually mean to level that particular accusation at you! I actually assumed you'd picked up that macro from reading tutorials etc, as it's quite common, due to MSVC 6 being very popular back in the day.
It was where I cut my teeth on C++ as well

^lol, no worries. I started C++ before .net and ended up picking up many bad programming practices. Back then, I was very smug and thought I knew everything (mostly because I stayed in a small town in the midwest where there was no one to challenge what little skill I had, or call me out on my BS). Even then, I had no excuse. That what you guys are for.

What if I used the stack instead?

Nope. First, you can't delete things on the stack. And second, even if you manually called the destructor, it's still undefined behavior. If you use the non-trivial destructor in any way before returning from the constructor, it's undefined behavior.

Well, I'd never call delete on a stack object. >.<

I was thinking of changing it to this so the constructor returns and the lifecycle begins:

"One objection to a “critique of C#” would be that you can’t talk about C# without talking about the whole “.Net experience”. However, one can approach the topic of Hitler without a complete discussion of Nationalist Socialism, so I feel justified." - Steve White.

"One objection to a “critique of C#” would be that you can’t talk about C# without talking about the whole “.Net experience”. However, one can approach the topic of Hitler without a complete discussion of Nationalist Socialism, so I feel justified." - Steve White.

Since it is a macro, which deals only with tokens and knows nothing about the other phases of compilation, it looks (literally) like this:

if(*(ptr++)) { delete *(ptr++); *(ptr++) = NULL; }

Notice how I only increment once in the invocation of the macro, but the token "x" is replaced with the macro parameter three times, resulting in it incrementing the pointer three separate times. This puts it past the end of the array, and causes undefined behavior.

Anybody (you, or someone else that uses your code) might make the assumption that this wouldn't happen. You might forget the body of the macro, and later on do something like I posted, which will crash the program. My policy on it is if the macro is to be used outside of that module that it was defined, it should not have any multiple-evaluation side effects; otherwise, I should provide it as a function, instead. I only use "unsafe" (in this sense) macros where I know what all of the inputs will be, and can guarantee that it will be safe.

The reason we're asking is because there are now better tools to do these things, and if you are not careful, this can come back to bite you. Also, most debuggers can't step into a macro invocation; they'll display the line where the macro was invoked, so it is up to you to figure out how the preprocessor expanded the macro, and what went wrong by hand, or dump the preprocessed output through compiler switches, and sift through it manually.

compiler doesn't have to push the parameters, call the function, pop them off the stack, do push the registers (eax, ebx, etc.), do whatever, restore the registers and return.

Also, I want to point out, that this is exactly what inline functions were designed to do. The body of the function is compiled "inline" where it is called. For your example, a macro and an inline function would have more or less the exact same result. Different compilers have different rules for what they will and won't inline, and how small the function must be for it to be inlined. However, for an inline function to do this task, it will do none of the things you mentioned, and essentially generate the same code that the macro would have, given that it was used correctly. It might even wind up being more efficient, by storing the result of x once and reusing it, rather than recalculating it three times, with potentially harmful side-effects.