The singleton is one of the simplest object-oriented C++ patterns. Probably due to its simplicity, it is also an often misused one. It is easy to implement your own, and therefore one might tend to use it a bit too often as a design choice. When should you use a singleton then? The answer is quite obvious, when you need a unique global object. However remember, global variables are usually frowned upon and you shouldn’t treat a singleton any other way. Avoid global variables as much as you can, but in some cases they are valid design decisions.

SingletonMeme

Global variables are considered bad practice, because they make it very hard to reason about code locally. The state of a global object might be changed anywhere in the program and therefore you usually cannot make any assumption about its state when looking at it in a local scope. However the singleton can be used to address two categories of design problems.
The first one is to represent physical objects or resources that are unique in the context of the program. E.g. imagine you are designing software that will run on a single car. So a Car object might be a singleton, since the software will never have to deal with fleet of cars (of course you could also envision a software that controls several cars, where you would not make Car a singleton).
The second category is objects that are global by design (without representing any physical object). E.g. you might decide to implement a resource manager as a singleton. There might be several instances of the resource itself, but the manager, which keeps track of the - limited - number of instances is a unique global object. Or as another example, loggers are often implemented as singletons since you want your whole program to log to the same instance.

So in short. Do not think if you should use a singleton or not, but instead think if your design should enforce that a certain object is global and unique.

So if after some consideration you still decided to go ahead with a singleton, let’s have a look on how to implement one.

For the sake of simplicity we will consider the associated data of the singleton to be an int.

Quick and Dirty

In the header:

struct Singleton {
  int& get() { return value_; }
private:
  int value_ = 0;
};

extern Singleton global_instance;

In the .cpp file:

Singleton global_instance;

But that really is just a global object. Nothing prevents the user from creating another Singleton instance. So let us change this slightly into the static singleton.

The Static Singleton

struct Singleton {
  int& get() { return value_; }
private:
  inline static int value_ = 0;
};

(Note that we used the nice C++17 feature of initializing an inline static data member.) Now the user might still create several instances of the singleton, but they are really nothing else than handles to the same static data member.

E.g. user code

Singleton one;
++one.get();
...
Singleton another;
int value = another.get();

And one and another will return references to the same data instance.

Of course you can take that a step further and make the get function also static to make the singleton nature of your class more clear (since not all your classes which are singletons will have Singleton in the name).

struct Singleton {
  static int& get() { return value_; }
private:
  Singleton() = delete;
  inline static int value_ = 0;
};

Which will make the usage look like this:

++Singleton::get();
...
int value = Singleton::get();

And thus hopefully a bit more readable and clear.

Now all is fine and well. Everything is static and the singleton is initialized before the program i.e. main() starts executing and is destroyed after it ends. But what if code in another static object uses our singleton? The order of initialization of static objects is generally undefined (implementation dependent). The standard only guarantees that all static objects defined in the same file will be initalized in order of their declaration. But as soon as they are spread out over several files, we do have a problem. E.g. imagine a singleton logger that makes use of singleton memory manager. How can we make sure the memory manager is initalized when we use it in the logger in the static part of our code?

The Meyers` Singleton

Named after Scott Meyers, this implementation of a singleton defers its initialization to its first use. Thus solving the above mentioned problem of undefined static object initialization order.

struct Singleton {
  static Singleton& instance() {
    static Singleton inst;
    return inst;
  }
  int& get() { return value_; }
private:
  Singleton() = default;
  ~Singleton() = default;
  Singleton(const Singleton&) = delete;
  Singleton& operator=(const Singleton&) = delete;
  int value_ = 0;
};

The private constructor prevents the program from directly constructing the object. Instead the initialization will take place only on the first call of the static instance() member function. Since this is the only way to access the singleton, it is guaranteed to be initialzed on the first usage.

The usage would look like this:

++Singleton::instance().get();
...
int value = Singleton::instance().get();

Looking at the instance() function you might have realized that we return a reference of a local variable (which is usually a really bad idea). However it is a reference to a static local variable, hence only one instance of this local variable exists in the entire program and therefore returning its reference is perfectly fine. And unlike a file static object, static local variables are initialized the first time they are used.

However there is one downside. There is a performance overhead. Every time the instance() function is called, there is an implicit check to see if the static variable is already initialized. So if you repeatedly need to access the singleton, it is best to store a reference to the returned instance. E.g.:

Singleton& inst = Singleton::instance();
for (auto i = 0; i < N; ++i) {
  do_something(inst.get());
}

The Pimpl Singleton

Sometimes it is desired to have a clear seperation between interface and implementation. The pointer to implementation (pimpl) idiom exposes only the interface in the header file, and the implementation is hidden inside a class inside the .cpp file. The actual singleton class then only holds a pointer to that implementation class (we will actually store a reference instead of pointer, but let’s call it pimpl anyway).

So a Pimpl Singleton looks like this. In the header:

struct SingletonImpl; // Forward declare
struct Singleton {
  Singleton();
  int& get();
private:
  static SingletonImpl& impl();
  SingletonImpl& impl_;
};

In the .cpp file:

// User code will not mind changes here as long as the interface stays the same
struct SingletonImpl {
  int value_ = 0;
};

Singleton::Singleton() : impl_(impl()) {}

int& Singleton::get() { return impl().value_; }

SingletonImpl& Singleton::impl(){
 static SingletonImpl instance;
 return instance;
}

Each singleton instance also saves a reference to the implementation instance to avoid the performance overhead mentioned in the previous section. (There is also a small overhead of an indirection).

How about thread safety?

Until now we ignored the thread safety of the singletons. But while the here presented singleton instances are thread safe, because they are static, the associated data members are no different than any other shared variable. When used in a multi-threaded context the programmer is responsible to use it in a thread safe manner, e.g. protecting them by a mutex.

For a more in-depth view into the subject I highly recommend the book referenced below. I hope now you know how to implement your singleton, but remember to think twice before deciding to chose a global variable in disguise.

References

  • Hands-On Design Patterns with C++ by Fedor G. Pikus (ISBN 978-1-78883-256-4)