There’s a horrible disease out there, and a lot of programmers – both junior and senior – are affected by it: Singletonitis. Please, let’s help stopping the plague from spreading further by reading and understanding this post.
What is Singletonitis? It is something that’s both bad for your code’s health, and bad for your own and your co-workers sanity. We will get to simple, easy-to-understand examples and counter-examples of Singletonitis gone bad in a minute.
When I talk about Singletonitis, I’m speaking of codebases that are so heavily infected with singletons, that their count is already in the high twenties – if you’re working on such a thing, I feel sorry for you. If you’re an experienced developer and teach inexperienced programmers how to put in even more of the damn things without giving them an alternative, go and hide in a corner, now!
To make that clear, by singletons I mean classes which can only ever be instantiated once (lazy or not), and manage their own state.
So why are singletons bad, exactly? Rather than coming up with things that have already been said elsewhere, I’d like to give concrete examples of where a singleton was clearly the wrong choice, and what the alternatives would have been.
Example: Singletons are bad for multi-threading
Face it, the days of running your games on a single core are over. Sooner or later everybody has to embrace multi-threading, no matter whether you’re working on the next Xbox/PlayStation, or on a handheld/mobile. Note that I’m not talking about creating singletons in a thread-safe manner, but rather how they’re used during run-time.
A classical example of an abused singleton I have seen in the past was called the render device most of the time: a singleton responsible for sending rendering commands/draw calls to the underlying API.
Why does a render device need to be a singleton? Right, because it makes it easy to access the rendering portion of the codebase from everywhere, making it easier for developers getting something rendered on screen. Wrong! All it does is create a strong coupling between code, with no apparent benefit.
You want to add multi-threaded rendering to the engine? Great, add a mutex/critical-section to each and every call of your singleton, because your render device is responsible for sending commands to the GPU. You can no longer use per-thread command-lists or such things, because that has to be handled by the render device internally.
Why not put the responsibility of preparing rendering commands into a separate class (e.g. called the RenderContext), which can be instantiated more than once? Each object that needs to render something get’s handed an instance to a RenderContext, and does the rendering.
The benefits? If you want to do single-threaded rendering (for debugging purposes, or if you’re working on single-CPU hardware), just instantiate one RenderContext, and hand that to other classes that need to render. On multi-core CPUs, instantiate a RenderContext for each thread, gather rendering commands, sort them, and dispatch them on the main-thread.
That wasn’t so hard, was it?
Example: Singletons need additional state
Another singleton I’ve seen in almost every engine is the so-called texture manager. Having something called a manager should already set of all kinds of warning bells, but what most people don’t realize is that as soon as you put something into a singleton, you need additional state to manage something which would otherwise be known implicitly.
What I mean by that? Let us assume you separate your textures into several groups, e.g. application-lfetime textures, textures only needed for the in-game GUI, textures only needed during a certain level, etc. I’m going to assume that you’re already doing something like that because you care about memory, memory fragmentation, and memory management in general, right? I certainly hope so.
Therefore, sooner or later you want to free all your level textures, because the player wants to return to the main menu. You certainly don’t want to leave any level-traces behind, do you?
Too bad, your texture manager is a singleton. But easily enough, we can add a flag/tag/identifier/whatever to our list of textures in the texture manager, so we know which type of texture it was, or where it was coming from. Then add a new method called PurgeByTag() which does the job.
“But the texture manager shouldn’t be worried about the origin of the texture!” I hear you say. Ok, what are your options? You could certainly store all level textures in e.g. a “std::vector<Texture*> levelTextures” yourself, and only remove those from the texture manager upon exiting a level. Maybe that floats your boat, great, but you’ve just partially duplicated the list stored inside the texture manager by storing it yourself outside of the manager class. I’m sure you agree that this breaks certain fundamental OOP principles.
Whatever you do, you’re just fixing symptoms, not the root cause of the problem: making the texture manager a singleton in the first place!
Well, if the texture manager hadn’t been a singleton, we could instantiate it more than once, and let the problem solve itself in a surprisingly simple way:
TextureLibrary* applicationTextures = new ... TextureLibrary* levelTextures = new ... // load textures into whatever library you want // ... delete levelTextures;
Deleting all level textures has become a single line, an absolute no-brainer, because the information needed to know whether they belong to the level or somewhere else is given implicitly. It’s that simple, and the same principle can be applied to ~80% of all the singletons I’ve seen in the past.
Example: Singletons make it easy to break stuff
Probably my most favourite example of an abused singleton is the file system.
Why must the file system be a singleton? Right, because it makes it so much easier to just open a file, read something from it, and be done with it. Happy developers, happy world. It probably was done out of good intentions, but only created more harm than good.
Having your file system implemented as a singleton doesn’t make things easier, but harder instead. You just enabled each and every developer on the team to open a file from any thread, killing your carefully crafted TRC/TCR/LOT-guideline implementation in the process. Telling everybody on the team to “please only read from files in this one function we’ve meticulously added to each class concerned with resource loading” is nothing more than a shitty attempt at fixing the symptoms, not the root – again.
If the file system can be instantiated more than once, problems like the above can be solved easily. If your resource manager (or whatever you want to call it) needs to open and read files, just let him instantiate his own file system. The resource manager can open up a new thread, use his file system in there, and load whatever files he wants. If you want, you can even get rid of mutexes/critical sections in this code path because only the resource manager can access his file system – it’s thread-safe implicitly, because it’s only ever accessed from the same thread.
Implement your TRC guidelines once, and be done with it. No need to worry about anybody breaking stuff over and over again.
Example: Singletons decrease performance
If you have lots of lazy initialized singletons, they will decrease your performance. I’ve seen lazy-initialized singletons show up during profiling more than once. The reason is that the compiler cannot fold several Singleton::Instance() calls into one because he can’t be aware of any side-effects happening. This means that each Singleton::Instance() call will at least cause a branch instruction, and possibly a cache-miss as well, because e.g. the isInstantiated member variable needs to be accessed. Both are notoriously bad on consoles, even more so if you have a lot of singletons.
Turning your lazy-initialized singleton into explicitly created singletons helps you getting rid of the aforementioned overhead, but raises the question why it’s a damn singleton then, for crying out loud?
I certainly could go on with lots and lots of abused singletons (yes, I have seen way to many of them in the past), but I’ll spare you the details. Instead, I sincerely encourage everyone to help getting rid of the singleton disease once and for all. They have done nothing but harm.
- Rip singletons out of your code, come up with a better design – it will help you in the long run. The examples mentioned above are just a few, and all of them create problems which could have been avoided.
- Don’t encourage others to implement even more of those bastards by handing them e.g. a Singleton<> class template, making the process even easier.
- Teach inexperienced programmers about the drawbacks of singletons, and help them improve their design. I would expect that from more experienced developers.