How do I implement a singleton class template in C++

  • 2020-11-20 06:12:12
  • OfStack

The singleton is one of the simplest design patterns. In practical engineering, if the object of a class has a high cost of repeatedly holding resources, and the external interface is thread-safe, we tend to manage it in singleton mode.

In this article we implement the correct singleton pattern in C++.

The selection

In C++, there are two options for the singleton pattern.

1 is a base class that implements a public constructor that is not available and provides a static interface such as GetInstance to access objects that are unique to a subclass. Because the subclass constructor must call the base class constructor, but the base class has no public constructor available, this allows the subclass object to be constructed only by the base class and its friends, thus guaranteeing the singleton mechanism. 2 is the implementation of a class template whose template parameters are the name of the class that you want to be managed by the singleton, and provides static interfaces such as GetInstance. The advantage of this approach is that you want classes managed by singletons to be free to write without having to inherit from the base class; And you can always take off your clothes if you need to.

This article chooses to implement a singleton class template, whose shape is as follows:


template <typename T>
struct Singleton {
 static T* get();
 T* operator->() const {
 return get();
 }
};

The member access operator is overloaded here in order to achieve this shorthand Singleton<T>()->func() .

Obviously, the core of the singleton implementation lies in static member functions T* get() .

A typical error implementation

A typical error implementation is to use what is known as double checking (double check).


#include <mutex>

template <typename T>
struct Singleton {
 static T* get() {
 static T* p{nullptr};
 if (nullptr == p) {
  std::lock_guard<std::mutex> lock{mtx};
  if (nullptr == p) {
  p = new T;
  }
 }
 return p;
 }
 T* operator->() const {
 return get();
 }

 private:
 static std::mutex mtx;
};

template <typename T>
std::mutex Singleton<T>::mtx;

The outer layer is checked to avoid locking too large an area, resulting in lock competition particularly frequent; The inner check is designed to ensure that initialization is done only when the other threads do not preempt the lock. This is true under Java, but not guaranteed under C++.

In addition, it is worth mentioning that the thread safety of p initialization here is guaranteed by the C++ standard. After C++11, the standard guarantees that the initialization of static members of a function is thread-safe; Reading and writing to it is not thread safe.

Use the facilities provided by the standard library

In the implementation of the singleton, we actually want to implement the "execute only once" semantics. After C++11, the standard Library actually provides such facilities. It is called std::once_flag and std::call_once . They implement such semantics by combining mutexes and condition variables internally. It is worth mentioning that if an exception is thrown during execution, the standard Library facility does not consider it a "successful execution." Other threads can then continue to preempt the lock to execute the function.

We use the standard library facility to implement this class template.


#include <mutex>

template <typename T>
struct Singleton {
 static T* get() {
 static T* p{nullptr};
 std::call_once(flag, [&]() -> void {
  p = new T;
 });
 return p;
 }
 T* operator->() const {
 return get();
 }

 private:
 static std::once_flag flag;
};

template <typename T>
std::once_flag Singleton<T>::flag;

So you can write something like this:


#include <mutex>
#include <iostream>
#include <future>
#include <vector>

#include "singleton.h"

struct Foo {
 void address() const {
 std::lock_guard<std::mutex> lock{mtx};
 std::cout << static_cast<void*>(const_cast<Foo*>(this)) << '\n';
 }
 mutable std::mutex mtx;
};

int main() {
 Singleton<Foo>()->address();
 std::vector<std::future<void>> futs;
 for (size_t i = 0; i != 10; ++i) {
 futs.emplace_back(std::async(&Foo::address, Singleton<Foo>::get()));
 }
 for (auto& fut : futs) {
 fut.get();
 }
 return 0;
}

The output is similar to this:


$ ./a.out
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10
0x7fbc6f405a10

Bonus: It's important to note that all std::once_flag Internally Shared with 1 pair of mutex and condition variables. So when there's a lot std::call_once Performance will degrade. So this is one point that I might want to pay attention to. However, if there are many std::call_once , which probably means the programming is not reasonable...

Bonus: Note that we did not release the object pointed to by p. This is because the C++ program destructs static variables in an indeterminate order. If there are dependencies between static variables, destructing the dependent object may result in a segment error. So they don't release it at all, which is called LeakySingleton . Of course, if one of your projects implements a universal ExitManager It's possible to destruct correctly. But consider the possibility of using a lot of third party libraries, which are unlikely to use what you've implemented ExitManager , it becomes impossible to manage the destruction of all static variables, so you just leave it alone.

And so on and so on

If you read this article carefully, you might suddenly realize that you just read this sentence: "After C++11, the standard guarantees that static members of a function are thread-safe to initialize; reading and writing to them is not thread-safe."

In that case, why should we bother to use it T* get()0 and std::call_once ? Direct use static hack a singleton class template is not good?


template <typename T>
struct Singleton {
 static T* get() {
 static T ins;
 return &ins;
 }
 T* operator->() const {
 return get();
 }
};

Above is how to implement a singleton class template in C++ in detail, more information about c++ singleton class template please pay attention to other related articles on this site!


Related articles: