The Singleton Pattern in C++

A singleton is a class that does not allow for dynamic instantiation. At runtime, one instance of the class is statically initialized, and this instance cannot be copied or assigned. To use the object, an accessor method is provided within the class definition to allow another portion of the program to grab a reference. Here is an example class definition that uses this pattern:

// singleton.h
#include <string>

class Singleton {
 public:
  /// Deleted Singleton Copy Constructor
  Singleton(Singleton const&) = delete;

  /// Deleted Singleton Assignment Operator
  void operator=(Singleton const&) = delete;

  /// Static accessor method
  static Singleton& Instance() {
    static Singleton singleton;
    return singleton;
  }
  
  void SetValue(const std::string& str) {
    this.value_ = str;
  }

  std::string GetValue() {
    return value_;
  }

 private:
  /// Privatizing the Singleton default constructor
  Singleton() = default;
  /// Dummy member field for demonstration
  std::string value_;
}

After defining a class with the singleton pattern, it can be used like so:

// main.cpp
#include <iostream>
#include "singleton.h"

int main() {
  auto singleton = Singleton::Instance();
  singleton.SetValue("Hello World!");
  std::cout << singleton.GetValue() << std::endl;
  return 0;
}

The main.cpp file calls Singleton::Instance() which returns a reference to the statically initialized singleton object and assigns the reference to singleton. From there, we now have a reference to the static singleton object that we can use to access or mutate its member fields. Calling SetValue() allows us to pass in a std::string to mutate the singleton’s value_ member variable. Calling GetValue() returns the now-modified member variable and prints it to stdout.

Benefits

The singleton pattern is great for instances where multiple parts of a program need global access to a single resource. Such examples would include a global configuration service or a database management interface; there is hardly a necessity to have multiple copies of such classes, so it would be better to have one instance that is globally accessible to all portions that require its use.

This pattern can also prevent unnecessary memory allocation; by making it impossible to create more than one instance of a class that uses this pattern, it is guaranteed that memory will not have to be allocated more than once for the class (of course, this does not take into account possible memory allocations for dynamically resizable and allocatable member fields).

By ensuring one instance of a class, the program only has one interface for interacting with and accessing the resource. This alleviates having to deal with multiple interfaces or representations of a particular type; calling the Instance method for the class is enough.

Drawbacks

Shared, globally accessible resources can be great for single-threaded programs, but multi-threaded applications can quickly run into race condition issues. To counteract this, the singleton would need to be made thread-safe, but this can increase the complexity of the class design.

Design patterns are great for easily replicable code, but some patterns can be overused or incorrectly used. The singleton pattern can oftentimes be overused; sometimes, a simple, standard class definition would suffice. It is critical that this pattern only be leverage when it is deemed truly necessary.

Nonetheless, the singleton, if used correctly, can greatly improve the design and flow of a program. The benefits often outweigh the drawbacks, but as with any design pattern, it is up to the developer to analyze and determine its effectiveness and applicability to the task at hand.

One thought on “The Singleton Pattern in C++”

Leave a comment