Class instances gone wrong!

Started by
25 comments, last by Servant of the Lord 10 years, 11 months ago

Dear C++ newbies, experts and all of you people in between,

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! sad.png

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:


game_app_t* app = NULL;

void exit_func()
{
    if( app )
        delete app;
}

//-----------------------------------------------------------------------------
// Name: main
// Desc: Program entry point.
//-----------------------------------------------------------------------------
int main( int argc, char* argv[] )
{
    atexit( exit_func );
    app = new game_app_t;

    return 0;
}

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:



#include "game.h"
#include <assert.h>


/* Useful macros */
#define safe_delete(x) if(x) { delete x; x = NULL; }
#define fail_safe(x) if(!x) return;

/* Global class instance */
game_app_t* This = NULL;


/* Default constructor/deconstructor */
game_app_t::game_app_t()
{
    this->init();
}

game_app_t::~game_app_t()
{
    this->uninit();
}

/* Initialization and uninitialization */
bool game_app_t::init()
{
    /* Set global class instance */
    This = this;
    
    /* Initialize OpenGL */
    m_ogldrv = new ogldrv_t();
    assert( m_ogldrv != NULL );
    
    if( !m_ogldrv->init( 0, NULL, 640, 480, GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH, "Loop-til" ) )
        return false;
    
    /* Set default callbacks */
    glutDisplayFunc( this->display_func );
    glutIdleFunc( this->idle_func );
    glutKeyboardFunc( this->keyboard_func );
    glutReshapeFunc( this->reshape_func );
    glutMouseFunc( this->mouse_click_func );
    glutPassiveMotionFunc( this->mouse_move_func );
    
    /* Start the main loop */
    glutMainLoop();
    
    return true;
}

void game_app_t::uninit()
{
    safe_delete( m_ogldrv );
}

/* Event callback functions */
void game_app_t::display_func()
{
    fail_safe(This->m_ogldrv);
    
    /* Clear the rendering buffers */
    This->m_ogldrv->clear_buffers();
    
    /* Swap the double buffer */
    This->m_ogldrv->swap_buffers();
}

void game_app_t::idle_func()
{
    glutPostRedisplay();
}

/* Blah blah blah */

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:


void game_app_t::exit_func()
{
    safe_delete(This);
}


/* Added after setting all the other callbacks */
atexit( this->exit_func );

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. ph34r.png

Shogun.

Advertisement

I have a question. Why are these macros and not methods or even functions?


/* Useful macros */
#define safe_delete(x) if(x) { delete x; x = NULL; }
#define fail_safe(x) if(!x) return;
 

Beginner in Game Development?  Read here. And read here.

 

Another question. Why are you using


/* Global class instance */
game_app_t* This = NULL;
 

and not


int main( int argc, char* argv[] )
{
    app = new game_app_t();
    /* where game_app_t is an actual class */
    return 0;
}
 

Beginner in Game Development?  Read here. And read here.

 

Why 'new' tongue.png
int main()
{
    game_app_t app;
}

I have a question. Why are these macros and not methods or even functions?

/* Useful macros */
#define fail_safe(x) if(!x) return;
 


That would make a pretty good WTF as a function ;)

void fail_safe(void* x) { if(!x) return; }
void game_app_t::display_func()
{
    fail_safe(This->m_ogldrv);
    
    /* Clear the rendering buffers */
    This->m_ogldrv->clear_buffers();
    
    /* Swap the double buffer */
    This->m_ogldrv->swap_buffers();
}

Why 'new' tongue.png


int main()
{
    game_app_t app;
}

I have a question. Why are these macros and not methods or even functions?


/* Useful macros */
#define fail_safe(x) if(!x) return;
 

That would make a pretty good WTF as a function ;)

void fail_safe(void* x) { if(!x) return; }
void game_app_t::display_func()
{
    fail_safe(This->m_ogldrv);
    
    /* Clear the rendering buffers */
    This->m_ogldrv->clear_buffers();
    
    /* Swap the double buffer */
    This->m_ogldrv->swap_buffers();
}

Be nice. I haven't C++'d since college. But I've been VB.net and C#'ing for years now :D 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. And read here.

 

Wow, you guys are mean! tongue.png

I have a question. Why are these macros and not methods or even functions?

/* Useful macros */
#define safe_delete(x) if(x) { delete x; x = NULL; }
#define fail_safe(x) if(!x) return;

Does it matter?

Another question. Why are you using


/* Global class instance */
game_app_t* This = NULL;
 

and not


int main( int argc, char* argv[] )
{
    app = new game_app_t();
    /* where game_app_t is an actual class */
    return 0;
}
 

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". smile.png

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.

Shogun

Does it matter?

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. And read here.

 

Does it matter?

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:


#include "cool_middleware.h"
namespace hodgman
{
  template<class T> void safe_delete(T* x) { if(x) x->Release; }
 
  void Frobnicate(Foo* foo)
  {
    foo->DoStuff();
    safe_delete(foo);
  }
}

This as soon as I #include your code in this file, my code breaks because of the macro! It becomes quite different:


 
  void Frobnicate(Foo* foo)
  {
    foo->DoStuff();
    if(foo) { delete foo; foo = NULL; }
  }

If you'd used an inline function for safe_delete, then everything would be fine

Further reading:

http://www.parashift.com/c++-faq/inline-vs-macros.html

http://www.parashift.com/c++-faq/macros-with-if.html

http://www.parashift.com/c++-faq/macros-with-multi-stmts.html

http://www.parashift.com/c++-faq/macros-with-token-pasting.html

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

This should probably be safe_delete(T*& x) as otherwise you're only assigning the local variable to NULL.

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

As you can see, I'm really old fashioned. No one ever told me that delete NULL was valid, so I'm going to gladly take your advice.

Shogun.

This topic is closed to new replies.

Advertisement