Article directory
brief introduction
Singleton mode definition: ensure that a class has only one instance, and provide a global access point to access the unique instance.
There are three key points in the single case mode:
- This class can only have one instance;
- It must create the instance itself;
- It must provide this instance to the entire system itself.
From the perspective of specific implementation, it can be divided into the following three points:
- Provide a private constructor (to prevent external calls from constructing instances of the class)
- Provide a static private object of this class
- Provides a static public function to create or get its own static private object (for example: GetInstance())
In addition, there are some key points (need more attention, easy to ignore):
- Thread safety (double checked locking)
- Resource release
classification
We declare the default constructor private, so that it won't be new by the external, and even the destructor can be private, so that only you can delete yourself. The singleton mode is very easy to implement. You can initialize instance directly in the static area, and then return it through getInstance. This is called the starved Chinese singleton class. There are also some ways to write new instance in getInstance and then return, which is called lazy singleton class, but it involves a judgment problem of the first getInstance.
There are about two ways to achieve this: lazy and hungry.
Lazy man: so it's named Siyi. You can't instantiate a class until you have to. That is to say, you can't instantiate a class until you first use it;
Hungry man: you must be hungry. Therefore, instantiation is performed when the singleton class is defined.
Starved Chinese (local static variable)
This method is very simple to implement, and there is no need to worry about the destruction of single cases.
// singleton.h #ifndef SINGLETON_H #define SINGLETON_H // A single case in the non real sense class Singleton { public: static Singleton& GetInstance() { static Singleton instance; return instance; } private: Singleton() {} }; #endif // SINGLETON_H
However, this is not a single case in the true sense. When using the following method to access a single example:
Singleton single = Singleton::GetInstance();
This will cause a class copy problem, which violates the characteristics of singleton. The reason for this problem is that the compiler will generate a default copy constructor to support class copying.
To avoid this problem, there are two solutions:
- Change the return type of the GetInstance() function to a pointer, not a reference.
- Explicitly declares the copy constructor of the class and overloads the assignment operator.
For the first method, you only need to modify the return type of GetInstance():
// singleton.h #ifndef SINGLETON_H #define SINGLETON_H // Single case class Singleton { public: // Change return type to pointer type static Singleton* GetInstance() { static Singleton instance; return &instance; } private: Singleton() {} }; #endif // SINGLETON_H
Since the compiler will generate a default copy constructor, why not let the compiler not? This leads to a second way:
// singleton.h #ifndef SINGLETON_H #define SINGLETON_H #include <iostream> using namespace std; // Single case class Singleton { public: static Singleton& GetInstance() { static Singleton instance; return instance; } void doSomething() { cout << "Do something" << endl; } private: Singleton() {} // Constructor (protected) Singleton(Singleton const &); // No need to achieve Singleton& operator = (const Singleton &); // No need to achieve }; #endif // SINGLETON_H
In this way, we can ensure that there is only one instance without considering the problem of memory recovery.
Singleton::GetInstance().doSomething(); // OK Singleton single = Singleton::GetInstance(); // Error failed to compile
Lazy (create object pointer)
In lazy style, if you use multithreading, there will be thread security risks. In order to solve this problem, we can introduce double lock DCL mechanism.
// singleton.h #ifndef SINGLETON_H #define SINGLETON_H #include <iostream> #include <mutex> using namespace std; // Single example - lazy type / hungry type public use class Singleton { public: static Singleton* GetInstance(); private: Singleton() {} // Constructor (protected) private: static Singleton *m_pSingleton; // Pointer to singleton static mutex m_mutex; // lock }; #endif // SINGLETON_H
// singleton.cpp #include "singleton.h" // Single case lazy type (DCL mechanism of double check lock) Singleton *Singleton::m_pSingleton = NULL; mutex Singleton::m_mutex; Singleton *Singleton::GetInstance() { if (m_pSingleton == NULL) { std::lock_guard<std::mutex> lock(m_mutex); // Self unlocking if (m_pSingleton == NULL) { m_pSingleton = new Singleton(); } } return m_pSingleton; }
Resource release:
// Single case - active release static void DestoryInstance() { if (m_pSingleton != NULL) { delete m_pSingleton; m_pSingleton = NULL; } }
Then call the interface manually when release is needed:
Singleton::GetInstance()->DestoryInstance();
4. Summary of single case mode
The characteristics of lazy style:
Non multithreaded security
Advantage: the first call is initialized to avoid memory waste.
Disadvantages: you must lock (share how to lock in thread safety section) to ensure single instance, but locking will affect efficiency.
Characteristics of the starved Han style:
Multithreading security
Advantage: without lock, the execution efficiency will be improved.
Disadvantages: class is initialized when it is loaded, which wastes memory.
Overall benefits:
- The singleton pattern provides strict creation and access to unique instances
- The implementation of singleton mode can save system resources
Overall disadvantages:
- If an instance is responsible for multiple responsibilities but must be unique, the responsibilities of a single instance class are too much, which violates the principle of single responsibility
- Thread safety mechanism should be considered in multithreading
- The singleton mode has no abstraction layer and is not easy to expand
Applicable environment:
The system only needs one instance object
Only one provider is allowed for an instance
Common scenarios
The singleton mode is often used in combination with the factory mode, because the factory only needs to create a product instance, and there will be no conflict in the multi-threaded environment, so only one factory instance is needed.
Advantage
1. Reduce the cost of time and space (the cost of new instance).
2. The encapsulation is improved, so that the external instance is not easy to change.
shortcoming
1. Laziness is a way of exchanging time for space.
2. The hungry Han style is a way of exchanging space for time.
C++Implementation code #ifndef _SINGLETON_H_ #define _SINGLETON_H_ class Singleton{ public: static Singleton* getInstance(); private: Singleton(); //Make copy constructor and = operator private to prevent copying Singleton(const Singleton&); Singleton& operator=(const Singleton&); static Singleton* instance; }; #endif #include "Singleton.h" Singleton::Singleton(){ } Singleton::Singleton(const Singleton&){ } Singleton& Singleton::operator=(const Singleton&){ } //Initialize here Singleton* Singleton::instance = new Singleton(); Singleton* Singleton::getInstance(){ return instance; } #include "Singleton.h" #include <stdio.h> int main(){ Singleton* singleton1 = Singleton::getInstance(); Singleton* singleton2 = Singleton::getInstance(); if (singleton1 == singleton2) fprintf(stderr,"singleton1 = singleton2\n"); return 0; } //Result output: singleton1 = singleton2