1, Singleton mode
Single example is probably the most commonly used and simple design mode. There are various implementation methods, and different writing methods according to different needs. At the same time, single example has its limitations, so many people are against using single example. This paper summarizes the common writing methods of C + + singleton, including lazy style, thread safety, singleton template, etc., introduces them step by step according to the way from simple to complex, and finally returns to simple, and expounds the limitations of various implementation methods, such as intelligent pointer, magic static, thread lock, etc; From the beginning to the end, it is very helpful to learn and consolidate the features of C + + language.
1. What is a single example
Singleton is one of the design patterns, which is characterized by providing only one instance of a class and global variables. The only instance can be obtained at any location through the interface. The specific application scenarios are as follows:
-
Device manager: there may be multiple devices in the system, but only one device manager is used to manage device drivers;
-
Data pool, the data structure used to cache data, needs to be written in one place, read in multiple places or write in multiple places, read in multiple places;
2. When to use singleton mode
You need to use singleton only when there is a global variable of a class with only one instance in the system.
3. The realization of singleton mode in C + +
Basic points
-
There is only one instance in the global: the static feature, and the user is forbidden to declare and define the instance himself (set the constructor to private)
-
Thread safety
-
Prohibit assignment and copying
-
User gets instance through interface: using static class member function
Five realizations
(1) A flawed slob
Lazy initialization method does not instantiate an object until it is used, that is to say, it does not create a singleton object until the geInstance() method is called. The advantage is that if called, memory is not consumed.
/* * C++Single example mode of design mode * * version1: * with problems below: * 1. thread is not safe * 2. memory leak * */ #include <iostream> using namespace std; class Singleton{ private: static Singleton* m_pInstance; private: Singleton(){ cout << "constructor called!" << endl; } Singleton(Singleton&)=delete; Singleton& operator=(const Singleton&)=delete; public: ~Singleton(){ cout << "destructor called!" << endl; } static Singleton* getInstance() { if (m_pInstance == nullptr) { m_pInstance = new Singleton; } return m_pInstance; } }; Singleton* Singleton::m_pInstance = nullptr; int main() { Singleton* instance1 = Singleton::getInstance(); Singleton* instance2 = Singleton::getInstance(); return 0; }
Output results:
constructor called!
It can be seen that the constructor of a class is called only once after obtaining the instance of the class twice, indicating that only a unique instance has been generated. This is a single instance implementation of the most basic version. What are his problems?
-
Thread safety: when multiple threads get a single instance, it may cause a race condition: the first thread judges that m ﹣ pinstance is empty in the if, and then starts to instantiate the single instance; at the same time, the second thread also tries to get a single instance. At this time, it judges whether m ﹣ pinstance is empty, and also starts to instantiate the single instance. In this way, two objects will be instantiated, This is the origin of thread safety; solution: lock
-
Memory leak. Note that the class is only responsible for the new out object, but not for the delete object, so only the constructor is called, but the destructor is not called; therefore, memory leak will be caused. Solution: use shared pointer;
Therefore, an improved, thread safe, intelligent pointer implementation is provided here;
(2) A lazy single example of thread safety and memory safety (smart pointer, lock)
#include <iostream> #include <memory> // shared_ptr #include <mutex> // mutex // version 2: // with problems below fixed: // 1. thread is safe now // 2. memory doesn't leak class Singleton{ public: //typedef std::shared_ptr<Singleton> Ptr; using Ptr = std::shared_ptr<Singleton>; ~Singleton(){ std::cout<<"destructor called!"<<std::endl; } Singleton(Singleton&)=delete; Singleton& operator=(const Singleton&)=delete; static Ptr getInstance(){ // Double lock if(m_pInstance==nullptr){ std::lock_guard<std::mutex> lk(m_mutex); if(m_pInstance == nullptr){ m_pInstance = std::shared_ptr<Singleton>(new Singleton); } } return m_pInstance; } private: Singleton() { std::cout << "constructor called!" << std::endl; } private: static Ptr m_pInstance; static std::mutex m_mutex; }; // initialization static variables out of class Singleton::Ptr Singleton::m_pInstance = nullptr; std::mutex Singleton::m_mutex; int main(){ Singleton::Ptr instance1 = Singleton::getInstance(); Singleton::Ptr instance2 = Singleton::getInstance(); return 0; }
Output result
constructor called! destructor called!
shared_ptr and mutex are both C++11 standards. The advantages of the above method are
-
Based on the shared PTR, the RAII idea advocated by C + + is used to manage resources with objects. When the shared PTR is deconstructed, the new objects will also be delete d. This avoids memory leaks.
-
Lock is added and mutex is used to achieve thread safety. Here, we use the technology of two if judgment statements called double check lock. The advantage is that only when the judgment pointer is empty, we can lock it to avoid locking every time we call the get ﹣ instance method. After all, the cost of locking is still a little high.
The disadvantages are: using smart pointer requires users to use smart pointer, unnecessary and unnecessary constraints; using lock also has overhead; at the same time, the amount of code has increased, and we hope that the simpler the implementation, the better.
There are more serious problems, in some platforms (related to compiler and instruction set architecture), double check lock will fail!
So there is a third way to achieve thread safety based on magic static
(3) The most recommended lazy singleton (magic static) - local static variable
class Singleton { public: ~Singleton(){ std::cout<<"destructor called!"<<std::endl; } Singleton(const Singleton&)=delete; Singleton& operator=(const Singleton&)=delete; static Singleton& getInstance(){ static Singleton instance; return instance; } private: Singleton(){ std::cout<<"constructor called!"<<std::endl; } }; int main(int argc, char *argv[]) { Singleton& instance_1 = Singleton::getInstance(); Singleton& instance_2 = Singleton::getInstance(); return 0; }
This method is also called Meyers' Singleton A single case of Meyer's Meyers, the famous author of the series of Effective C + +. The features used are in the C++11 standard Magic Static Characteristic:
If control enters the declaration concurrent while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
This ensures that concurrent threads must be initialized when obtaining static local variables, so it has thread safety.
The lifetime of C + + static variables is from declaration to the end of the program, which is also a lazy type.
This is the most recommended single instance implementation method: thread safety is guaranteed through the characteristics of local static variables, shared pointers are not needed, and the code is concise; note that when using, it is necessary to declare a single instance reference & to get objects.
In addition, someone on the Internet returns a pointer instead of a reference
static Singleton* get_instance(){ static Singleton instance; return &instance; }
This is not good because it is impossible to avoid the user using delete instance to destroy the object in advance. It is recommended that you use the method of return reference.
(4) Implementation of singleton pattern template class
#include <iostream> template<typename T> class Singleton{ public: static T& getInstance(){ static T instance; return instance; } virtual ~Singleton(){ std::cout<<"destructor called!"<<std::endl; } Singleton(const Singleton&)=delete; Singleton& operator =(const Singleton&)=delete; protected: Singleton(){ std::cout<<"constructor called!"<<std::endl; } }; /********************************************/ // Example: // 1.friend class declaration is requiered! // 2.constructor should be private class DerivedSingle : public Singleton<DerivedSingle>{ // !!!! attention!!! // needs to be friend in order to // access the private constructor/destructor friend class Singleton<DerivedSingle>; public: DerivedSingle(const DerivedSingle&)=delete; DerivedSingle& operator =(const DerivedSingle&)= delete; private: DerivedSingle()=default; }; int main(int argc, char* argv[]){ DerivedSingle& instance1 = DerivedSingle::getInstance(); DerivedSingle& instance2 = DerivedSingle::getInstance(); return 0; }
Output result
constructor called! destructor called!
The above implements a single template base class. The use method is as shown in the example. The subclass needs to pass itself to the Singleton template as the template parameter T; at the same time, the base class needs to be declared as a friend, so that the private constructor of the subclass can be called.
The key points of the implementation of the base class template are as follows:
-
The constructor needs to be protected so that the subclass can inherit;
-
Used Singular recursive template patternCRTP(Curiously recurring template pattern)
-
get instance method and static local method of 2.2.3.
-
In this case, the destructor of the base class does not need virtual, because the subclass only uses the Derived type in its application, which ensures that the type of the destructor is consistent with that of the constructor
(5) Implementation methods that do not need to declare friends in subclasses
stay stackoverflow On, there is a big God that gives the method that does not need to declare friends in the subclass, which is released here; The essence is to use a proxy class token, the subclass constructor needs to pass the token class to construct, but protect the token, and then the subclass constructor can be public. This subclass only has the constructor of Derived(token), so the user can not define an instance of a class by himself, and play the role of controlling its uniqueness. The code is as follows.
#include <iostream> template<typename T> class Singleton{ public: static T& getInstance() noexcept(std::is_nothrow_constructible<T>::value){ static T instance{token()}; return instance; } virtual ~Singleton() =default; Singleton(const Singleton&)=delete; Singleton& operator =(const Singleton&)=delete; protected: struct token{}; // helper class Singleton() noexcept=default; }; /********************************************/ // Example: // constructor should be public because protected `token` control the access class DerivedSingle : public Singleton<DerivedSingle>{ public: DerivedSingle(token){ std::cout<<"destructor called!"<<std::endl; } ~DerivedSingle(){ std::cout<<"constructor called!"<<std::endl; } DerivedSingle(const DerivedSingle&)=delete; DerivedSingle& operator =(const DerivedSingle&)= delete; }; int main(int argc, char* argv[]){ DerivedSingle& instance1 = DerivedSingle::getInstance(); DerivedSingle& instance2 = DerivedSingle::getInstance(); return 0; }
The above are 5 design schemes of single interest mode. It is recommended to use the third one. If the project is large and single instance reuse is required, the fourth one can be used.