Project Z probably marks the first time I’ve felt that it’s okay to use singleton objects. It feels quite liberating to be able to do this; when is there ever going to be more than one Client, or more than one PhysicsManager, or more than one Console? Generally, never, so you don’t want to have to worry about the case when it might happen. There’s other benefits, too; generally, it’s very hard to bind nonstatic member callbacks to function pointers. Static functions make it infinitely easier.
However, static members come with an insidious bug. I first encountered it trying to debug a Sqrat problem within their library itself. It’s been explained in fair detail here, but the way I’m using these static members together presents the problem in a fashion which is even more tricky to think about.
Consider a KeyListener and a Client class. The KeyListener has a static list of KeyListeners which map inputs to different abstract actions. The Client class initializes a Player, which in turn asks the KeyListener to add a listener to its list and return it to the Player. If the initialization order is this, then everything’s fine:
-
KeyListener
-
Client
The KeyListener is initialized, and it initializes its static list of KeyListeners. Then, the Client creates the Player, who calls the KeyListener’s static newKeyListener() function. That function adds a KeyListener to KeyListener::listeners, which is a static member. Everything succeeds.
However, it doesn’t always happen like this. Different C++ libraries can differ in their initialization orders, so it might initialize like this too:
-
Client
-
KeyListener
In this case, the Client creates the Player. The Player then calls that same newKeyListener() function. However, this time, KeyListener::listeners doesn’t exist, as it’s never been initialized, so the function really just plops down a KeyListener somewhere random. Then, the KeyListener class is initialized, and KeyListeners::listeners is actually created. At this point, you look at your GDB traces, and you wonder why your KeyListener suddenly ceased to exist without even having its destructor called.
This actually happened to me; the program didn’t even segfault when newKeyListener() tried to add to its nonexistent list. It just silently failed, and then overwrite the KeyListener that had been added to the list with empty space. The simplest fix to this problem is to wrap all your static members in static functions. That is, instead of doing this:
//KeyListener.h
class KeyListener {
...
static std::vector listeners;
}
//KeyListener.cpp
std::vector KeyListener::listeners;
do this:
//KeyListener.h
class KeyListener {
...
static std::vector &listeners;();
}
//KeyListener.cpp
std::vector &KeyListener;::listeners() {
static std::vector listeners;
return listeners;
}
This will cause any call to listeners() to construct the static variable listeners, so that no matter when you need it, it will be initialized.