Stands for, “You wanna see some…stuff?” It was a phrase we often used whenever we discovered something horrifying wonderful about C++. Well, this post is going to be chock full of that. First, let’s talk about the keyword static.
Static
There’s a lot that this simple keyword can mean:
Class Static Variable
### in Thing.h ###
struct Thing
{
static int sNumber;
}
### in Thing.cpp ###
int Thing::sNumber = 0;
This is a variable that is under the name of the class itself, rather than an specific object. Since there’s no time it’s really “created”, it has to be initialized somewhere. Thus, we initialize it in the cpp.
These are pretty gross, and there’s a pretty insidious bug that crops up if you start to use these with objects. It might exist in your code right now and not be showing any symptoms! We’ll get to it in a second.
Class Static Function
### in Thing.h ###
struct Thing
{
static int GetNumber();
}
### in Thing.cpp ###
int Thing::GetNumber()
{
return 0;
}
Now in your code, you can call Thing::GetNumber() and it will return 0. No objects needed!
Function Static Variable
int CountTimesThisIsCalled()
{
static int sCount = 0;
return sCount++;
}
If you haven’t seen this before, this can look kind of crazy, especially in more complex code. That variable exists when the function is called and retains its value on subsequent calls. So calling that function five times would set count to 5.
Time to talk about a controversial pattern: The Singleton.
The Singleton Pattern
The singleton pattern lets you give all parts of the code the same object from a single getter function. Think of large systems in your game. You don’t need multiple graphics systems, do you? Well, then you can create a getter that retrieves it!
You can also have the singleton lazy load.You don’t necessarily need to turn on the logger, right? So, don’t create and initialize it if you never log anything!
Here’s one awful way to implement this pattern:
### in Thing.h ###
struct Thing
{
// A pointer to a singleton we're going to create
static Thing sThing*;
}
### in Thing.cpp ###
// Initializing sThing in the cpp
Thing Thing::sThing* = new Thing();
This uses class static variables to implement the singleton pattern. The problem here is pretty subtle. Let’s say there’s six other singletons just like these that are going to be created. What order are they going to be created in? The answer is it completely depends. If those singletons need to call functions on each other in a certain order, you might crash. For a short answer, read this. That’s right, your code might already be broken. That link even has a great explanation of how to solve this problem.
In short, we’re going to use function static variables and class static functions.
### in Thing.h ###
struct Thing
{
static Thing& GetInstance();
}
### in Thing.cpp ###
Thing& GetInstance()
{
static Thing sThing;
return sThing;
}
This code actually implements the singleton pattern. It lazy loads the Thing class, rather than creating it before main even runs.
Alright, still with me? That was the minor YWSSS. Time for the good stuff.
CRTP
The original idea for the following classes came from here.
Take a second and think about this one. Does this code compile?
template <typename T>
struct SingletonObject
{
};
struct Monkey : SingletonObject<Monkey>
{
};
Sure. Why not? All we’re doing is passing the type along to a base class. It looks incredibly strange, but this absolutely works! This is a technique known as the Curiously Reoccurring Template Pattern. It just kept on being useful so it got its own acronym. Let’s muck with it a bit:
template <typename T>
struct SingletonObject
{
public:
// Add a getter function that returns a reference to a pointer.
// This allows us to change what it points to!
static T*& GetSingleton();
protected:
// We mark this as protected so that it can be called by a base class.
SingletonObject();
}
template<typename T>
inline SingletonObject<T>::SingletonObject()
{
// If we've never created this singleton, we create it.
if (GetSingleton() == nullptr)
GetSingleton() = static_cast<T*>(this);
// If we've already created this singleton, we throw because someone was trying to make it twice!
else
throw "Tried to create a second instance of singleton";
}
template <typename T>
inline T*& SingletonObject<T>::GetSingleton()
{
// Remember, we're returning a reference to a pointer, so while this seems to always
// return nullptr, we can let the SingletonObject() constructor create the instance!
static T* obj = nullptr;
return obj;
}
struct Monkey : SingletonObject<Monkey>
{
Monkey();
}
Take some time to stare at that. It’s missing some pieces that are pretty vital, but its the smallest I can make it to get the idea across. Keep in mind that you do need to create the class first. This won’t make it for you by calling GetSingleton()!
Let’s look at the order of events here:
- Monkey class constructor is called somewhere in code.
- This calls the base class, SingletonObject<Monkey>.
- The SingletonObject<Monkey> constructor stores a pointer to itself (which is actually a Monkey class)
- Later, someone else calls Monkey::GetSingleton() (because it’s a public function on SingletonObject<Monkey>) and is given back the one reference that was created!
If we use SingletonObject on some other class, let’s say Rabbit, would this overwrite it? Nope! We’d be accessing a completely different static class! We’re not accessing SingletonObject, we’re access SingletonObject<Rabbit>!
Here‘s a link to the full version of the class.
Some important notes about the full version is that we don’t want to give access to the pointer to just anyone. Thus, we have a separate internal function that can muck with the pointer and a public one that users can have.
We also have a destructor that will set the pointer to null so a new singleton can be created if needed.
We can take this concept even further. What if we want to store every instance that is made of a class? We can simply add them to a map and remove them on destruction! Does your graphics system need to know what all the point lights are in a scene?
class PointLight : AutoLister<PointLight> {}
Done. Now in code you can simply call PointLight::GetList() to get all instances of the point light class in the game. Need all of the enemies? All of the physics objects? All of the sound emitters?
You could even store something related to them. Want to have just a list of all of the names of enemies? Create one that stores a set of strings. You could store pointers to owners. There’s a lot of possibilities with this concept!
We can clean this up a bit so it’s easier to update in code:
#define gGraphics (Engine::GraphicsSystem::GetSingleton())
Now you don’t need to write a short story every time you want to access your singleton! Further, it makes it much easier to sub that out later if you, let’s say, swap from OpenGL to Vulkan graphics!
Now that we’ve had some fun and learned something pretty useful, it’s time to talk about the thing that game developers seem to absolutely hate. Testing. I promise we’ll do another YWSSS after!