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.
another problem of singletons: the order of creation and shutting them down…. if one singleton uses another the fun really starts, especcially in bigger projects.
but for some things I like the global character of the singleton without using global variables, for example in your example with textures it’s much better to know one hard limit for texture memory(wrapped in a class to release textures when too much memory is used) instead of having a hard limit in every texturelibrary (okay, another solution would be that all texture libraries are using static members and functions for the management; but then your texturelibrary class handles two different areas of functionality). another thing: if you write an debug-html generator it’s much easier to use the singleton approach instead of having to hand over pointers to many internal objects to get the debug data.
amen as well.
I’m currently fighting those beasts on a day to day basis; as you very well know from my rants 🙂
although i have to admit i’m starting to be disappointed and getting bored by the simple failures of my good friend the Singleton. My new hero of anti patterns is mutually dependant Singletons. Yes, they do exist,.. unfortunately.
I don’t think the singleton aspect of “can only be instantiated once” is even the reason at all for its abuse.
I think for many inexperienced developers, it’s the convenience of it – it’s just about accessible from just about anywhere without passing over any instances as function parameters. They don’t really care about the “only one instance” aspect, or rather, it is just secondary to them.
They might be aware (ok, or maybe not…) of the problems, but most counter examples do not fix the problem of “global accessibility without passing a bazillion of parameters”, so they – for the sake of convenience (or lazyness) – take the negatives (and its bag of problems) just so that they can have an easier life by using a pre-tailored “solution” and not having to think about a system’s design so much.
This. I know I’ve done it more than once because of these reasons. It doesn’t make me proud and I’ve never done this kind of stuff on big projects (most of those already came with their own atrocious devices like factories from hell), but for smaller stuff I must confess I used some singletons here and there.
I’ve written an article about this topic on my technical blog, with a proposal for an alternative approach to “conceptual singleton” implementation: http://javapeanuts.blogspot.com/2012/02/singleton-testing-and-dependency.html
I try to avoid singletons as i can, but my ever confusion is the logger classes. Singleton or not? 🙂
Even though logging is mostly a global concern, you don’t need to put it into a singleton class. Alternatives:
– A namespace with free functions (Log, Error, Warning, …)
– A namespace where instances of *any* class can get registered, as long as they offer a Log() function or similar. This allows to e.g. create an EnemyAiLogger instance on the stack, which will then be automatically used for logging until the instance goes out of scope. Loggers can be added to/removed from a fixed-size array holding space for up to 32 loggers or something.
– Same as the second alternative, but storing loggers in an intrusive linked-list instead. This is what I use.
Can you elaborate on a “namespace where instances of *any* class can get registered, as long as they offer a Log() function or similar”? I may simply be sleep deprived but i’m just not getting how this would work.
Work against an abstract interface, and offer two free functions (in a namespace) for registering/unregistering a logger, which is an implementation of the interface. The loggers can be stored in an intrusive linked-list, and every log-function just walks the list and calls the corresponding function on each implementation.
As an alternative to the linked-list, you can use a fixed-sized array instead – less jumping around memory, but with log functions, a few extra cache misses are generally not the performance bottleneck anyway.
Oh, and using a simple RAII helper class, registering can be done in the constructor, unregistering in the destructor. This allows you to add loggers in any scope (e.g. locally on the stack), making them log stuff while they’re in this scope, and nowhere else. Can be handy at times.
Singletons tightly couple your application. In the case that your singleton touches a data store e.g. Filesystem, Database, Url etc. you’re screwed. You can’t mock the thing and any unit test you write for it instantly becomes less reliable. If you need to limit an object to a single instance, look at composition patterns and maybe an IoC container or something like MEF.
All valid and good points!
I’ve been there and felt the pain. There’s nothing quite like having to confront your own mistakes and admit to them for providing the motivation to do things better.
Somehow I get the feeling that I don’t quite know what you’re trying to say :):
I never advocated the use of Singletons, quite the contrary. I had to deal with them on a day-to-day basis, and hated every single one of them.
Pingback: Đừng giết chương trình của bạn bằng Singleton | lesslucifer
I don’t personally see the Singleton to be too bad. Granted using them is completely limited. I’ll agree that they are abused and used in the worst positions possible. The only time I actually use a singleton, is for my engine’s game weird as heck state system. And really for a few decent reasons.
Pingback: Глобальные объекты и места их обитания - itfm.pro