Patterns in Software Development: The Singleton Pattern

Patterns are an important abstraction in modern software development. They offer well-defined terminology, clean documentation, and learning from the best. The most controversial design pattern from the book “Design Patterns: Elements of Reusable Object-Oriented Software” (Design Pattern) is the Singleton Pattern. I would like to introduce it first before discussing its pros and cons.




Rainer Grimm has worked as a software architect, team leader and training manager for many years. He likes to write articles on the programming languages ​​C++, Python and Haskell, but also often speaks at specialist conferences. On his blog Modernes C++, he deals intensively with his passion for C++.



The singleton pattern is a generational pattern. Here are the facts in a nutshell:

  • Asserts that only one instance of a class exists

  • You must have access to a common resource and
  • this shared resource can only exist once.
  • Boost serialization defines a template, “which will convert any class into a singleton with the following functions“.
  • The template performs its task by being defined by the class boost::noncopyable emits:

class BOOST_SYMBOL_VISIBLE singleton_module :
    public boost::noncopyable
{
  ...
};



instance (static)

  • A private instance of Singleton

getInstance (static)

  • A public member function that returns an instance
  • Creates the instance

Singleton

Called to the member function getInstance is the only way Singleton to create. Moreover, it is allowed Singleton does not support copy semantics.

Which brings me to its implementation in modern C++.

In the following lines, I discuss different implementation variants of the singleton. I would like to start with the classic implementation of the singleton pattern.

The following implementation is based on the “Design Patterns” book:

// singleton.cpp

#include 

class MySingleton{

  private:
    static MySingleton* instance;                        // (1)
    MySingleton() = default;
    ~MySingleton() = default;

  public:
    MySingleton(const MySingleton&) = delete;
    MySingleton& operator=(const MySingleton&) = delete;

    static MySingleton* getInstance(){
      if ( !instance ){
        instance = new MySingleton();
      }
      return instance;
    }
};

MySingleton* MySingleton::instance = nullptr;            // (2)


int main(){

  std::cout << '\n';

  std::cout << "MySingleton::getInstance(): "
    << MySingleton::getInstance() << '\n';
  std::cout << "MySingleton::getInstance(): "
    << MySingleton::getInstance() << '\n';

  std::cout << '\n';

}

The original implementation used a protected Standard constructor. I also explicitly deleted the copy semantics (copy constructor and copy assignment operator). I will write more about copy semantics and move semantics later. The program's output shows that there is only one instance of the class MySingleton is.



This implementation of the singleton requires the C++11 standard.

With C++17, the declaration (1) and definition (2) of the static instance variable instance done directly in class:

class MySingleton{

  private:
    inline static MySingleton* instance{nullptr};  // (3)
    MySingleton() = default;
    ~MySingleton() = default;

  public:
    MySingleton(const MySingleton&) = delete;
    MySingleton& operator=(const MySingleton&) = delete;

    static MySingleton* getInstance(){
      if ( !instance ){
        instance = new MySingleton();
      }
      return instance;
    }
};

(3) performs the declaration and definition in one step. What about the weaknesses of this implementation? The static initialization order failure and concurrency are obvious.

Static variables within a translation unit are initialized in the order in which they are defined. However, there is a serious problem with the initialization of static variables between translation units. If a static variable staticA in a translation unit and another static variable staticB is defined in another translation unit and staticB staticA needed to initialize itself, the static initialization order fiasco tends to happen: the program is buggy because there is no guarantee which static variable is initialized first at runtime.

For completeness: static variables are initialized in the order in which they were defined and destroyed in the reverse order. Consequently, there is no guarantee of the order of initialization or destruction between translation units. A translation unit is the result of execution of the preprocessor.

How does this relate to singletons? Singletons are static variables in disguise. So if the initialization of a singleton depends on the initialization of another singleton in another translation unit, this can lead to the static initialization order failure.

Before I write about the solution, I want to show you the problem in action.

A 50/50 chance of getting it right

How are the static variables initialized? It takes place in two steps: at compile time and at run time. If a static variable cannot be initialized to a constant at compile time, it is zero-initialized. At runtime, dynamic initialization now occurs for those static elements that were zero-initialized.

// sourceSIOF1.cpp

int square(int n) {
    return n * n;
}

auto staticA  = square(5); 

// mainSOIF1.cpp

#include 

extern int staticA;                  // (1)
auto staticB = staticA;     

int main() {
    
    std::cout << '\n';
    
    std::cout << "staticB: " << staticB << '\n';
    
    std::cout << '\n';
    
}

(1) declares the static variable staticA. The following initialization of staticB depends on the initialization of staticA away. However will staticB Zero-initialized at compile time and dynamically initialized at runtime. The problem is that there is no guarantee of which order staticA or staticB is initialized due to staticA and staticB belong to different translation units. This gives a 50/50 chance of it staticB 0 or 25 is. To demonstrate this problem, I changed the link order of the object files. This also changes the value of staticB!



What a failure! The output of the program depends on the link order of the object files. How can we solve this problem?

Static variables with local scope are created when they are first used. Local scope essentially means that the static variable is surrounded by curly braces in some way. This lazy initialization is a guarantee that C++98 offers. Meyers Singleton is based precisely on this idea. Instead of a static instance of the type Singleton it has a local static type Singleton.

// singletonMeyer.cpp

#include 

class MeyersSingleton{

  private:

    MeyersSingleton() = default;
    ~MeyersSingleton() = default;

  public:

    MeyersSingleton(const MeyersSingleton&) = delete;
    MeyersSingleton& operator = (const MeyersSingleton&) = delete;

    static MeyersSingleton& getInstance(){
      static MeyersSingleton instance;        // (1)
      return instance;
    }
};


int main() {

  std::cout << '\n';

  std::cout << "&MeyersSingleton::getInstance(): "
    << &MeyersSingleton::getInstance() << '\n';
  std::cout << "&MeyersSingleton::getInstance(): "
    << &MeyersSingleton::getInstance() << '\n';

  std::cout << '\n';

}

static MeyersSingleton instance in (1) is a static variable with local scope. Consequently, it is lazily initialized and cannot fall victim to the static initialization order failure. With C++11, Meyer's singleton becomes even more powerful.

competition

With C++11, static variables with local scope are also initialized in a thread-safe manner. This means that Meyer's singleton not only solves the static initialization order failure, but also guarantees that the singleton is initialized thread-safely. It is also the easiest and fastest way to use a local static variable for thread-safe initialization of a singleton. I've already written two articles on thread-safe initialization of the singleton:

The singleton pattern evokes many emotions. My English article "Thread-Safe Initialization of a Singleton" has been read more than 300,000 times so far. Therefore, in my next articles I would like to present the advantages and disadvantages of the singleton and possible alternatives.


(rm)

To the home page

Leave a Comment