So much for the new year’s resolution to write some sort of an update every week. That went out the window pretty quickly. Especially now that I’ve taken over the Inner Product column for Game Developer Magazine and that’s taking away some of my writing time (check out the May issue for my first column!).
It turns out that Charles’ old article Stupid C++ Tricks: Adventures in Assert is one of our most viewed entries, even after all this time. So I figured I’d follow it up with a really, really simple C++ trick. It’s almost trivial, really, but I’ve totally fallen in love with it. At first, when Charles introduced me to it, I was kind of lukewarm. But now I’m finding myself going through refactoring rampages in the code changing things to be this way. Intrigued? Read on.
C++ enums are great in practice, but they’re a bit lacking once you really start flexing them. Don’t get me wrong, they’re definitely a step up from #defines, but you can’t help but wish for more. Type safety is only so-so (enums get promoted to ints silently), you can’t forward declare them so you’re forced to have extra includes, you can’t split up a declaration of enums across multiple files (the compiler would have to do more work, but it would be awesome to be able to create uids for different classes in their own header files instead of a global one). But the most grating of design decisions is their scoping rules.
Take for example a set of enums describing the game flow:
enum GameFlowType
{
Run,
Exit,
Restart,
Restore,
};
Now functions can return a GameFlowType to indicate that the main loop should do. So far so good.
Except that, look at how it’s used:
GameFlowType DoMainLoop(bool render)
{
//...
if (someCondition)
return Exit;
return Run;
}
Ouch! We’ve managed to pollute the global namespace with totally generic keywords such as Run and Exit. What if I want to have an enum that controls what action an AI is going to take:
enum AIAction
{
Enter,
Exit,
Stop,
Walk,
Run,
};
That won’t even compile because the symbols conflict with the GameFlowType ones. Even worse, it will compile just fine if they don’t happen to be included in the same compilation unit. So you might be fine all along until you write some AI code that has control over the game flow. Oops!
OK, so you can prefix all your enums like this:
enum GameFlowType
{
GameFlowTypeRun,
GameFlowTypeExit,
GameFlowTypeRestart,
GameFlowTypeRestore,
};
and
enum AIAction{
AIActionEnter,
AIActionExit,
AIActionStop,
AIActionWalk,
AIActionRun,
};
That will technically fix the problem, but it’s a bit ugly, and we’re still polluting the global namespace (what if I wanted to have a class called AIActionEnter?).
Another potential solution is to score the enum inside the class that is using them. So we could have:
class AIAction{
public:
enum AIActionType
{
Enter,
Exit,
Stop,
Walk,
Run,
};
};
Much cleaner, do doubt, but also with its share of problems: First of all, it forces you to associate a set of enums with a class, which is not something you always want to do. But the biggest problem is that as soon as you have more than one set of enums per class, they all get promoted to the same scope:
class AIAction
{
public:
enum AIActionBehavior
{
Enter,
Exit,
Stop,
Walk,
Run,
};
enum AIActionGroup
{
None,
Self,
Team,
All,
};
};
When we see one of those enums in code referred to as AIAction::Self, we really have no idea that it’s referring to that group the action is applied to, as opposed to AIAction::Enter, which is the type of action.
Our solution? Move all enums into a descriptive namespace.
namespace GameFlowType
{
enum Enum
{
Invalid,
Run,
Exit,
Restart,
Restore,
};
}
When you have a variable of that enum type, it’s listed as GameFlowType::Enum type, which clearly indicates what it does. And its values are referred to as GameFLowType::Exit. You get the ideal scoping, you’re not tied to any particular class, things are explicit but not verbose, you’re no polluting the global namespace, and it’s doesn’t affect the runtime or compilation times.
Sometimes the best solutions are the simplest ones.