When to use it: Singleton pattern

We’ll start the discussion of design patterns with the object creation patterns.  First up is the Singleton pattern.  Conceptually, this is used when you want exactly one instance of an object.  A common example is a logger.  Sometimes an application wants all its components to log data to the same destination.  So, developers might create a Singleton logger, then all the components can easily get a handle to its instance and use its API.  But the Singleton pattern has significant drawbacks, and there’s usually better methods for handling situations where you want a Singleton.

Singleton in C++

In C++, a simple single-threaded Singleton typically looks like this, which performs lazy initialization:


Logger.h
=========
class Logger {
public:
  static Logger* GetInstance() const;

  void log(char *msg);
protected:
  Logger();
  static Logger *instance_;
  ...
};
Logger* Logger::instance_ = nullptr;
Logger.cpp
==========
Logger* Logger::GetInstance() {
  if (instance_ == nullptr) {
    instance_ = new Logger();
  }

  return instance_;
}
...

What we have here is a static variable wrapped in a class.  A traditional local or global Logger can’t be created because the constructor is protected.  Instead, client code has to get an instance with the GetInstance() static method, which performs a lazy initialization of the static variable.  Thus, during execution, this implementation allows exactly 0 or 1 instance of a Logger to exist on the heap. It can be globally accessed by any code by calling Logger::GetInstance().

So it’s worth asking what’s the value of this when compared to a simple global variable?  With a Singleton, there’s a guarantee of exactly 0 or 1 instances of the class at any given time, and there’s a difference in when the class is initialized.  On the other hand, the two techniques are similar in that they provide globally-accessible state.

Very important note! It’s tempting to write a lazy initialized Singleton template class and create singletons like this: Singleton<Logger>::GetInstance().  This gives you the worst of Singletons and global variables. You basically have global state, but without the guarantee of single instantiation, because there’s nothing preventing local or global instances of Logger.

Analysis

Search around online even a little bit and you’ll see that there’s some mostly deserved hate for the Singleton pattern for two reasons – it’s easy to misuse and it’s hard to test.  In the misuse category, there’s temptation to use a Singleton for things like:

  • Caching (ex: storing results of specific db queries).  This is better handled by a simple memory sharing scheme, like having all db objects keep a (smart) pointer to some memory on the heap.  This is easily enforced if the db objects are created using a factory or prototype.  A Singleton for this purpose would create a globally accessible cache, but typically only the interface objects need to read or write the cache.
  • Read-only resource sharing (ex: list of valid words, or a configuration file).  In my opinion, this is usually better handled with a local variable that is passed to the objects/methods that need it.  Again, using a Singleton for something like this would create globally accessible state, but usually only a few components need access to the read-only data.  In the case of a configuration file, sometimes a component needs only a small subset of the configuration data.  In this case, just that data can be passed to the component, rather than making the entirety of the configuration file available globally as a Singleton does.
  • Replacing global variables.  Singletons are card-carrying members of the Gang of Four patterns book, and they provide global state.  So, if we just replace the bad global variables with the good design pattern, there’s no more problem, right?  Well, no – we still have most of the same problems that global variables introduce: it’s not always immediately clear which components use the global so any component could affect any other, and it can be hard to mock the global/Singleton which makes testing difficult.

The reason that code using Singletons is hard to test is because the dependency of an object on a Singleton can be hidden.  Since the object is global, an object can get ahold of a Singleton without any mention in its API.

So let’s remove the hidden dependency drawback from the analysis by saying that all testers and developers are aware of the dependency.  A typical method used to isolate the code under test is to mock the dependencies.  Let’s say that we have a Singleton named A.  GetInstance() returns a pointer to the instance of A.  Therefore, it’s possible to configure the Singleton to return a pointer to a MockA which inherits from A, and everything is fine – in fact, one might claim that this pattern enables good testing patterns.

On the other hand, now we can make the argument that if we need to be able to change our dependencies, perhaps we should use a different technique for managing the dependencies, such as Dependency Injection.  The dependency can be make explicit (rather than hidden), which may reduce bugs and complications when using the code.

When to use it

So we understand the significant problems with Singletons – even if there is a valid use for it, it still introduces problems with testing code that uses it.  What are the reasons to use a Singleton?  This can be broken into two questions: what are the valid reasons to use global state, and what are the valid reasons to restrict instantiation of an object to exactly 0 or 1 copies?

A valid reason to use global state is that the state is truly needed globally.  Perhaps many components in many layers need access to the data or object, such as a logger or a component that analyzes performance.  This is especially relevant if the object hierarchies or call trees are deep.  When there are many layers, passing a local variable to every component of the hierarchy adds a parameter to many constructors, methods, or functions.  Often it’s simply much more work to add the parameters, and it doesn’t necessarily make the code more robust or performant.  In this case, global state may be the best option.

Reasons to restrict the object to exactly 0 or 1 copies of an object include:

  • Large initialization cost.  Perhaps there is a large initialization cost of an object, and you want to control exactly when the object will be initialized.  This can be done with a Singleton.
  • Large ongoing resource use.  Perhaps the object consumes a lot of memory and you don’t want (or the machine can’t handle) more than one of these objects at a time.  Or, maybe the object kicks off a CPU-intensive thread, and you only want one of these threads running at a time.  These would be good reasons to restrict the number of instantiations of the object to 0 or 1.
  • Rare usage.  If the cost of an object is large, and the code only rarely uses it (as in, if the executable is run N times, the object may be used in only a small percentage of those executions), it could be especially valuable to use a lazy initialized Singleton.  The object will not be instantiated most of the time, so those resources could be used by other components.

Furthermore, there could be other considerations with using a Singleton.  A Singleton can be fairly easily converted to a Factory by changing the implementation of GetInstance(), so if it’s likely that the “0 or 1 instance” requirement will change to “N instances,” a Singleton might save some work in the future.  And, as previously mentioned, a Singleton can easily return an interface rather than a class, which could allow it to return an object or its mock depending on context.  This could be useful for testing, even if there are other options for managing dependencies.

So I propose this two part test for when to use a Singleton.  A component should satisfy both of these conditions to be considered for a Singleton.

  1. Many components at many levels need access to the object’s state (global state is needed)
  2. Large object cost, especially if it’s not always needed during execution (large initialization/resource cost, especially combined with rare use).

If condition 1 is not satisfied but condition 2 is, then a local variable that is passed through parameters is probably preferable because it makes dependencies explicit.  If condition 2 is not satisfied but condition 1 is, then a simple global variable might be reasonable, or local variables that are instantiated where needed should be considered.

Conclusion

Singletons have their place in code, but it’s easy to misuse them and can introduce more problems than they solve.  I propose a two part test to determine if the situation really requires a Singleton, or if the situation can be handled by more explicit or simpler solutions such as local variables that are passed through parameters or instantiated where needed, or simply global variables.

It should be clear that Singletons can provide benefits to an application or library, but the benefits need to be considered against simpler or different options for providing the same benefit, and also the inherent drawbacks with using global state.

Please leave any feedback or questions in the comments, I’d love to hear your thoughts on Singletons!

Leave a Reply

Your email address will not be published. Required fields are marked *