Problems with Singleton Pattern

C++Design PatternsSingleton

C++ Problem Overview


I've been reading about Singleton pattern for last few days. The general perception is that the scenarios where it is required are quite few (if not rare) probably because it has its own set of problems such as

  • In a garbage collection environment it can be an issue with regards to memory management.
  • In a multithreaded environment it can cause bottlenecks and introduce synchronization problems.
  • Headache from testing prespective.

I'm starting to get the ideas behind these issues but not totally sure about these concerns. Like in case of garbage collection issue, usage of static in singleton implementation (which is inherent to the pattern), is that the concern? Since it would mean that the static instance will last till the application. Is it something that degrades memory management (it just means that the memory allocated to the singleton pattern won't be freed)?

Ofcourse in a multithreaded setup, having all the threads being in contention for the singleton instance would be a bottleneck. But how does usage of this pattern causes synchronization problems (surely we can use a mutex or something like that to synchronize access).

From a (unit?)testing perspective, since singletons use static methods (which are difficult to be mocked or stubbed) they can cause problems. Not sure about this. Can someone please elaborate on this testing concern?

Thanks.

C++ Solutions


Solution 1 - C++

In a garbage collection environment it can be an issue with regards to memory management

In typical singleton implementations, once you create the singleton you can never destroy it. This non-destructive nature is sometimes acceptable when the singleton is small. However, if the singleton is massive, then you are unnecessarily using more memory than you should.

This is a bigger issue in languages where you have a garbage collector (like Java, Python, etc) because the garbage collector will always believe that the singleton is necessary. In C++, you can cheat by delete-ing the pointer. However, this opens its own can of worms because it's supposed to be a singleton, but by deleting it, you are making it possible to create a second one.

In most cases, this over-use of memory does not degrade memory performance, but it can be considered the same as a memory leak. With a large singleton, you are wasting memory on your user's computer or device. (You can run into memory fragmentation if you allocate a huge singleton, but this is usually a non-concern).

In a multithreaded environment it can cause bottlenecks and introduce synchronization problems.

If every thread is accessing the same object and you are using a mutex, each thread must wait until another has unlocked the singleton. And if the threads depend greatly upon the singleton, then you will degrade performance to a single-thread environment because a thread spends most of its life waiting.

However, if your application domain allows it, you can create one object for each thread -- this way the thread does not spend time waiting and instead does the work.

Headache from testing prespective.

Notably, a singleton's constructor can only be tested once. You have to create an entirely new test suite in order to test the constructor again. This is fine if your constructor doesn't take any parameters, but once you accept a paremeter you can no longer effective unit teest.

Further, you can't stub out the singleton as effectively and your use of mock objects becomes difficult to use (there are ways around this, but it's more trouble than it's worth). Keep reading for more on this...

(And it leads to a bad design, too!)
Singletons are also a sign of a poor design. Some programmers want to make their database class a singleton. "Our application will never use two databases," they typically think. But, there will come a time when it may make sense to use two databases, or unit testing you will want to use two different SQLite databases. If you used a singleton, you will have to make some serious changes to your application. But if you used regular objects from the start, you can take advantage of OOP to get your task done efficiently and on-time.

Most cases of singleton's are the result of the programmer being lazy. They do not wish to pass around an object (eg, database object) to a bunch of methods, so they create a singleton that each method uses as an implicit parameter. But, this approach bites for the reasons above.

Try to never use a singleton, if you can. Although they may seem like a good approach from the start, it usually always leads to poor design and hard to maintain code down the line.

Solution 2 - C++

If you haven't seen the article Singletons are Pathological Liars, you should read that too. It discusses how the interconnections between singletons are hidden from the interface, so the way you need to construct software is also hidden from the interface.

There are links to a couple of other articles on singletons by the same author.

Solution 3 - C++

When evaluating the Singleton pattern, you have to ask "What's the alternative? Would the same problems happen if I didn't use the Singleton pattern?"

Most systems have some need for Big Global Objects. These are items which are large and expensive (eg Database Connection Managers), or hold pervasive state information (for example, locking information).

The alternative to a Singleton is to have this Big Global Object created on startup, and passed as a parameter to all of the classes or methods that need access to this object.

Would the same problems happen in the non-singleton case? Let's examine them one by one:

  • Memory Management: The Big Global Object would exist when the application was started, and the object will exist until shutdown. As there is only one object, it will take up exactly the same amount of memory as the singleton case. Memory usage is not an issue. (@MadKeithV: Order of destruction at shutdown is a different issue).

  • Multithreading and bottlenecks: All of the threads would need to access the same object, whether they were passed this object as a parameter or whether they called MyBigGlobalObject.GetInstance(). So Singleton or not, you would still have the same synchronisation issues, (which fortunately have standard solutions). This isn't an issue either.

  • Unit testing: If you aren't using the Singleton pattern, then you can create the Big Global Object at the start of each test, and the garbage collector will take it away when the test completes. Each test will start with a new, clean environment that's unnaffected by the previous test. Alternatively, in the Singleton case, the one object lives through ALL of the tests, and can easily become "contaminated". So yes, the Singleton pattern really bites when it comes to unit testing.

My preference: because of the unit testing issue alone, I tend to avoid the Singleton pattern. If it's one of the few environments where I don't have unit testing (for example, the user interface layer) then I might use Singletons, otherwise I avoid them.

Solution 4 - C++

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?"

Solution 5 - C++

About this unit testing concern. The main problems seems to be not with testing the singletons themselves, but with testing the objects that use them.

Such objects cannot be isolated for testing, since they have dependencies on singletons which are both hidden and hard to remove. It gets even worse if the singleton represents an interface to an external system (DB connection, payment processor, ICBM firing unit). Testing such an object might unexpectedly write into DB, send some money who knows where or even fire some intercontinental missiles.

Solution 6 - C++

I agree with the earlier sentiment that frequently they're used so that you don't have to pass an argument all over the place. I do it. The typical example is your system logging object. I typically would make that a singleton so I don't have to pass it all over the system.

Survey - In the example of the logging object, how many of you (show of hands) would add an extra arg to any routine that might need to log something -vs- use a singleton?

Solution 7 - C++

I wouldn't necessarily equate Singletons with Globals. Nothing should stop a developer from passing an instance of the object, singleton or otherwise, as a parameter, rather than conjure it out of the air. The intent of hiding its global accessibility could even be done by hiding its getInstance function to a few select friends.

As far as the unit testing flaw, Unit means small, so re-invoking the app to test the singleton a different way seems reasonable, unless I'm missing something the point.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTL36View Question on Stackoverflow
Solution 1 - C++carlView Answer on Stackoverflow
Solution 2 - C++Greg HewgillView Answer on Stackoverflow
Solution 3 - C++Andrew ShepherdView Answer on Stackoverflow
Solution 4 - C++jalfView Answer on Stackoverflow
Solution 5 - C++Rafał DowgirdView Answer on Stackoverflow
Solution 6 - C++RobertLView Answer on Stackoverflow
Solution 7 - C++no-opView Answer on Stackoverflow