My main argument against singletons is basically that they combine two bad properties.
The things you mention can be a problem, sure, but they don't have to be. The synchronization thing can be fixed, it only becomes a bottleneck if many threads frequently access the singleton, and so on. Those issues are annoying, but not deal-breakers.
The much more fundamental problem with singletons is that what they're trying to do is fundamentally bad.
A singleton, as defined by the GoF, has two properties:
- It is globally accessible, and
- It prevents the class from ever being instantiated more than once.
The first one should be simple. Globals are, generally speaking, bad. If you don't want a global, then you don't want a singleton either.
The second issue is less obvious, but fundamentally, it attempts to solve a nonexistent problem.
When was the last time you accidentally instantiated a class, where you instead intended to reuse an existing instance?
When was the last time you accidentally typed "std::ostream() << "hello world << std::endl
", when you meant "std::cout << "hello world << std::endl
"?
It just doesn't happen. So we don't need to prevent this in the first place.
But more importantly, the gut feeling that "only one instance must exist" is almost always wrong.
What we usually mean is "I can currently only see a use for one instance".
but "I can only see a use for one instance" is not the same as "the application will come crashing down if anyone dares to create two instances".
In the latter case, a singleton might be justified. but in the former, it's really a premature design choice.
Usually, we do end up wanting more than one instance.
You often end up needing more than one logger. There's the log you write clean, structured messages to, for the client to monitor, and there's the one you dump debug data to for your own use.
It's also easy to imagine that you might end up using more than one database.
Or program settings. Sure, only one set of settings can be active at a time. But while they're active, the user might enter the "options" dialog and configure a second set of settings. He hasn't applied them yet, but once he hits 'ok', they have to be swapped in and replace the currently active set. And that means that until he's hit 'ok', two sets of options actually exist.
And more generally, unit testing:
One of the fundamental rules of unit tests is that they should be run in isolation. Each test should set up the environment from scratch, run the test, and tear everything down. Which means that each test will be wanting to create a new singleton object, run the test against it, and close it.
Which obviously isn't possible, because a singleton is created once, and only once. It can't be deleted. New instances can't be created.
So ultimately, the problem with singletons isn't technicalities like "it's hard to get thread safety correct", but a much more fundamental "they don't actually contribute anything positive to your code. They add two traits, each of them negative, to your codebase. Who would ever want that?"