Eight principles of face object design
Singleton mode
Definition: ensure that a class has only one instance, and provide a global access point for the instance
1. Motivation
In software systems, some classes must ensure that they have only one instance in the system to ensure their logical correctness and efficiency
2. Realization
2.1 hungry Han style
The so-called hungry Chinese style means that initialization is instantiation, which can be used directly in the future. (thread safe in itself)
class single{ private: static single* p; single(){} ~single(){} public: static single* getinstance(); }; single* single::p = new single(); single* single::getinstance(){ return p; }
However, the potential problem is that the initialization order of no-local static objects (static objects outside the function) in different compilation units is uncertain. If the getInstance() method is called before initialization is completed, an undefined instance will be returned.
2.2 lazy
Lazy style refers to instantiating it for the first time and then directly using it. However, there will be thread safety problems when instantiating for the first time.
2.2. 1 normal version (i.e. thread non safe version):
class single{ private: single(){}; ~single(){} static single* p; public: static single* getinstance(); static single* initance(); }; static single* single::p = nullptr; singleton* singleton::initance(){ if (p == nullptr) { p = new single(); } return p; }
2.2. 2 thread safe version (the cost of locking is too high)
class single{ private: single(){ pthread_mutex_init(&mutex); }; ~single(){} static single* p; public: static pthread_mutex_t mutex; static single* getinstance(); static single* initance(); }; static single* single::p = nullptr; singleton* singleton::initance(){ pthread_mutex_lock(&mutex); // Lock if (p == nullptr){ p = new singleton(); } pthread_mutex_unlock(&mutex); return p; }
2.2. 3. Double check lock (due to unsafe memory read / write reorder)
2.2. 3.1 initial version:
class single{ private: single(){ pthread_mutex_init(&mutex); }; ~single(){} static single* p; public: static pthread_mutex_t mutex; static single* getinstance(); static single* initance(); }; static single* single::p = nullptr; singleton* singleton::initance(){ if (p == nullptr){ //Pre lock inspection pthread_mutex_lock(&mutex); if (p == nullptr) // Post lock inspection p = new singleton(); pthread_mutex_unlock(&mutex); } return p; }
This version seems perfect and confuses many people. However, due to the memory read-write reorder, the double check lock will fail
reorder:
The code has an instruction sequence, which is not always carried out in the way we think (compiler optimization). When the actual code is in the instruction layer, the instruction execution order may be different from what we assume;
For example, this Code: p = new singleton(); Will be divided into three steps (default order):
- Allocate memory first
- Call the constructor to initialize the memory allocated in the first step
- Give the memory address to p
The above is our hypothetical order, which may be reorder ed as:
- Allocate memory first
- Give the memory address to p
- Call the constructor to initialize the allocated memory
At this time, when the memory is given to the address p in the second step, p is not null; The problem comes. At this time, another thread or threads come in to check if (p == nullptr). At this time, p is not null and returns directly. But the p returned at this time cannot be used.
2.2. 3.2 improved version:
- java and C # use volatile keyword to solve the problem. The compiler will not reorder variables with volatile keyword during the assignment process
- vc + + also has a volatile implementation version, but only Microsoft can use it, not cross platform
Implementation of double check lock after C++11:
2.2. 3.2 elegant version:
Another more elegant singleton mode implementation is proposed in Effective C + + (Item 04), which uses the local static object in the function (static object in the function). This method does not need locking and unlocking operations. The instance is created only when the getinstance() method is accessed for the first time.
class single{ private: single(){} ~single(){} public: static single* getinstance(); }; single* single::getinstance(){ static single obj; return &obj; }
After the C++0x standard, the compiler is required to ensure the thread safety of internal static variables. Therefore, the implementation after C++0x is thread safe (G++4.0 and above are supported), and it still needs to be locked before C++0x.
3. Summary:
- For single thread, use 2.2 1 the normal version is enough
- For multithreading:
- Use = = 2.2 2. Thread safe version (the cost of lock is too high) = = there is no error, but the cost is too high
- When using ordinary double check locks, we should pay attention to the security problems brought by reorder and directly use the cross platform implementation of C++11 version
- After the C++ 0X standard, the thread safety of local static objects in functions can be used to ensure the overall thread safety
reference resources
- GeeKBand - Design Pattern Li Jianzhong
- <Effective C++>