C++ Singleton Template

Singleton implementations are mostly the same. A template makes the singleton pattern more convenient to use.


Analysis

The singleton pattern must ensure that resource initialization is thread-safe, which has led to the following approaches (selected from “Template Implementation of C++ Singleton Pattern” and “Is the Singleton Pattern Simple? But Can You Really Get It Right?”):

1. Direct Locking

1
2
3
4
5
6
7
8
// Thread-safe version, but the cost of locking is too high
Singleton* Singleton::GetInstance() {
    Lock lock; // Pseudo-code: lock
    if (instance == nullptr) {
        instance = new Singleton();
    }
    return instance;
}

Although this ensures initialization happens only once, the lock is acquired every time GetInstance is called, resulting in poor performance.

2. Double-Checked Locking

Since locking is unnecessary after initialization, we can add a check if (instance == nullptr) before locking. The resource is only locked and initialized if it hasn’t been initialized yet, and we check again before initialization to ensure that if multiple threads enter the first if simultaneously, only one thread will initialize the resource.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Double-checked locking, but unsafe due to memory read/write reordering
Singleton* Singleton::GetInstance() {
    // First check if it has been initialized; if so, the lock is never used
    if(instance==nullptr){
        Lock lock; // Pseudo-code
        if (instance == nullptr) {
            instance = new Singleton();
        }
    }
    return instance;
}

Looks perfect, doesn’t it? But a program after compiler optimization may not work as expected. instance = new Singleton(); can be divided into three steps:

  1. Allocate memory needed for a Singleton object.
  2. Construct the Singleton object at the allocated memory location.
  3. Assign the address of the allocated memory to the pointer instance.

Due to compiler optimizations, out-of-order memory reads and writes can occur, and only step 1 is guaranteed to execute first. If thread A executes steps 1, 3, 2 in that order, then after step 3, instance is no longer nullptr. Thread B will directly return instance;, but the object hasn’t been constructed yet. Once thread B uses this object, it will lead to a bug.

3. Eager Initialization

The two methods above allocate resources and initialize the object only on first use. This is also called lazy initialization — like the toad in Daming Lake, it hops only when poked. Instead, we can initialize before the program enters main, bypassing the thread-safety issue entirely.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template<typename T>
class EagerSingleton
{
private:
    static T* t_;

public:
    static T& GetInstance()
    {
        return *t_;
    }

    EagerSingleton(T&&) = delete;
    EagerSingleton(const T&) = delete;
    void operator= (const T&) = delete;

protected:
    EagerSingleton() = default;
    virtual ~EagerSingleton() = default;
};

template<typename T>
T* EagerSingleton<T>::t_ = new (std::nothrow) T;

However, this pattern also has a problem: the object is initialized even if it is never used. If the object’s resource cost is high, this wastes resources.

4. C++ 11 to the Rescue

Scott Meyers proposed a singleton pattern using C++’s static keyword in Effective C++, Item 4: Make sure that objects are initialized before they’re used. This implementation is concise and efficient. Its characteristics are:

  • The instance object is initialized only when the program first reaches the GetInstance function.
  • After C++ 11, variables qualified with static are guaranteed to be thread-safe.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#ifndef SINGLETON_H
#define SINGLETON_H

/**
 * \brief Singleton template
 * \tparam T The type to be used as a singleton
 *
 * \code{.cpp}
 * // Direct use (recommended)
 * Singleton<T>::GetInstance()
 *
 * // Inheritance derivation
 * class C : public Singleton<T>
 * \endcode
 */
template<typename T, typename... Args>
class Singleton {
 public:
  // Delete copy constructor, move constructor, copy assignment operator, and move assignment operator to prevent external copying, assignment, or moving of the object
  Singleton(const Singleton &) = delete;
  Singleton(Singleton &&) = delete;
  Singleton &operator=(const Singleton &) = delete;
  Singleton &operator=(Singleton &&) = delete;

  static T &GetInstance(Args &&... args) {
    static T instance(std::forward<Args>(args)...);
    return instance;
  }

 protected:
  Singleton() = default;
  virtual ~Singleton() = default;
};

#endif // SINGLETON_H

By deleting the singleton class’s copy constructor, move constructor, and operator=, we prevent the unique instance from being copied or moved. Not exposing the constructor and destructor ensures the singleton class cannot be instantiated through other means, while defining both as protected allows them to be inherited and used by subclasses.

5. AI’s Wisdom

The singleton template code in Section 4. C++ 11 to the Rescue has a problem — the singleton object exists in stack memory, which can be an unnecessary overhead if the class is large. To address this, I implemented the following template with AI assistance:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#ifndef SINGLETON_H
#define SINGLETON_H

#include <memory>
#include <mutex>

/**
 * \brief Singleton template
 * \tparam T The type to be used as a singleton
 *
 * \code{.cpp}
 * // Direct use (recommended)
 * Singleton<T>::GetInstance()
 *
 * // Inheritance derivation
 * class C : public Singleton<T>
 * \endcode
 */
template<typename T, typename... Args>
class Singleton {
 public:
  // Delete copy constructor, move constructor, copy assignment operator, and move assignment operator to prevent external copying, assignment, or moving of the object
  Singleton(const Singleton &) = delete;
  Singleton &operator=(const Singleton &) = delete;
  Singleton(Singleton &&) = delete;
  Singleton &operator=(Singleton &&) = delete;

  // Get instance function for the singleton template class, using perfect forwarding and std::call_once to create and return the singleton object
  static T *GetInstance(Args &&... args) {
    std::call_once(flag_, [&]() { instance_ = std::make_unique<T>(std::forward<Args>(args)...); });
    return instance_.get();
  }

 private:
  // Constructor and destructor of the singleton template class, privatized to prevent external creation or destruction of objects
  Singleton() = default;
  ~Singleton() = default;

  // Member variables of the singleton template class, including a smart pointer to the singleton object and a once_flag variable to ensure thread safety
  static std::unique_ptr<T> instance_;
  static std::once_flag flag_;
};

// Initialize static member variables of the singleton template class
template<typename T, typename... Args>
std::unique_ptr<T> Singleton<T, Args...>::instance_ = nullptr;

template<typename T, typename... Args>
std::once_flag Singleton<T, Args...>::flag_;

#endif // SINGLETON_H

References