Problem description
Now, regardless of the development of a large system (at least my current department is this), will bring a log function, in the actual development process, there will be a dedicated log module, responsible for writing the log, because anywhere in the system, we may want to invoke the Log module function, to write the log. So, how do you construct an instance of a log module? Do you have a log module instance every time new, write the log, and then delete, don't tell me you did it. In C + +, you can construct a global variable for a log module, so it can be used anywhere, yes, well. However, my development department's C + + coding specification is referenced by Google's coding specifications.
Global variables in the project can not be used, it is a time bomb, is an unsafe, especially in multi-threaded programs, there will be a lot of unpredictability; at the same time, the use of global variables, also does not conform to the object-oriented packaging principle, so in pure object-oriented language Java and C #, There is no purely global variable. So, how to solve this log problem perfectly, we need to introduce the singleton pattern in design pattern.
Single-Case mode
What is a singleton mode, in Gof's design pattern: The basis of reusable object-oriented software, is to say: Ensure that a class has only one instance and provides a global access point to access it. First, you need to ensure that there is only one instance of a class, and in a class, to construct an instance, you must call the class's constructor, so that in order to prevent an instance from being constructed externally by invoking the constructor of the class, you need to mark the access permission of the constructor as protected or private; To provide a global access point, you need to define a static function in the class that returns an instance of the unique construct within the class. The meaning is clear, the UML class diagram is represented as follows.
UML Class Diagram
Code implementation
Singleton mode, single from the UML class diagram, on a class, there is no complicated relationship. However, in a real-world project, there are many things to consider when using code implementations.
#include <iostream>using namespacestd;classsingleton{ Public: StaticSingleton *getinstance () {if(M_instance = =NULL) {m_instance=NewSingleton (); } returnm_instance; } Static voiddestoryinstance () {if(M_instance! =NULL) { Deletem_instance; M_instance=NULL; } } //This is just a operation example intgettest () {returnm_test; }Private: Singleton () {m_test=Ten; } StaticSingleton *m_instance; intm_test;}; Singleton*singleton:: m_instance =NULL;intMainintARGC,Char*argv []) {Singleton*singletonobj =Singleton:: getinstance (); cout<<singletonobj->gettest () <<Endl; Singleton::D estoryinstance (); return 0;}
This is the simplest, but also the most common way of implementation, but also the online blog of the implementation of the way, but, this way of implementation, there are many problems, such as: Do not take into account the problem of multithreading, in the case of multi-threaded, it is possible to create multiple singleton instances, the following version is an improved version.
Implementation two:
#include <iostream>using namespacestd;classsingleton{ Public: StaticSingleton *getinstance () {if(M_instance = =NULL) {Lock ();//C + + does not have a direct lock operation, please use the other library's lock, such as boost, here only to illustrate if(M_instance = =NULL) {m_instance=NewSingleton (); } UnLock (); //C + + does not have a direct lock operation, please use the other library's lock, such as boost, here only to illustrate } returnm_instance; } Static voiddestoryinstance () {if(M_instance! =NULL) { Deletem_instance; M_instance=NULL; } } intgettest () {returnm_test; }Private: Singleton () {m_test=0; } StaticSingleton *m_instance; intm_test;}; Singleton*singleton:: m_instance =NULL;intMainintARGC,Char*argv []) {Singleton*singletonobj =Singleton:: getinstance (); cout<<singletonobj->gettest () <<Endl; Singleton::D estoryinstance (); return 0;}
Here are two times m_instance = = null judgment, is borrowed from the Java Singleton mode implementation, the use of the so-called "double check lock" mechanism. Because a lock and unlock is required to pay the corresponding cost, and two times to judge, you can avoid multiple locking and unlock operation, but also ensure that thread safety. However, this method of implementation in the normal development of the project is very good, there is no problem? However, if the operation of big data, the lock operation will become a performance bottleneck, to this end, a new singleton mode implementation has emerged.
Implementation three:
#include <iostream>using namespacestd;classsingleton{ Public: StaticSingleton *getinstance () {returnConst_cast <singleton *>(m_instance); } Static voiddestoryinstance () {if(M_instance! =NULL) { Deletem_instance; M_instance=NULL; } } intgettest () {returnm_test; }Private: Singleton () {m_test=Ten; } Static ConstSingleton *m_instance; intm_test;};ConstSingleton *singleton:: m_instance =NewSingleton ();intMainintARGC,Char*argv []) {Singleton*singletonobj =Singleton:: getinstance (); cout<<singletonobj->gettest () <<Endl; Singleton::D estoryinstance ();}
Because static initialization is initialized by the main thread in a single-threaded manner at the beginning of the program, that is, before entering the main function, the static initialization of the instance guarantees thread safety. When performance requirements are high, you can use this approach to avoid the resource waste caused by frequent lock and unlock. As a result of the above three implementations, the destruction of the instance should be taken into account, and the destruction of the instance will be analyzed later. As a result, there is a fourth way of achieving this:
Implementation four:
#include <iostream>using namespacestd;classsingleton{ Public: StaticSingleton *getinstance () {StaticSingleton m_instance; return&m_instance; } intgettest () {returnm_test++; }Private: Singleton () {m_test=Ten; }; intm_test;};intMainintARGC,Char*argv []) {Singleton*singletonobj =Singleton:: getinstance (); cout<<singletonobj->gettest () <<Endl; Singletonobj=Singleton:: getinstance (); cout<<singletonobj->gettest () <<Endl;}
The above is four kinds of mainstream single-case mode of implementation, if we have any good way to achieve, I hope you can recommend to me. Thank you, sir.
Instance Destruction
Of the four methods mentioned above, the other three are used except for the fourth one, which is not instantiated with the new operator; Our general programming concept is that the new operation needs to be matched with the delete operation; Yes, that's the right idea. In the above implementation, is to add a destoryinstance static function, which is the simplest, the most common method of processing; But, many times, it's easy to forget to call the Destoryinstance function, just like you forgot to call the delete operation. Because of the fear of forgetting the delete operation, so there is a smart pointer, then, in the singleton model, there is no "smart singleton", what should I do? What to do?
Let me start with the actual project, in the actual project, especially client development, actually do not care about the destruction of this instance. Because, the global is such a variable, the whole is used, its life cycle with the software life cycle, the software is over, it will naturally end, because after a program is closed, it will release the memory resources it occupies, so there is no so-called memory leak. However, there are cases where instance destruction is required:
- In the class, there are file locks, file handles, database connections, and so on, which are not immediately closed as the program closes, and must be manually released before the program is closed;
- A programmer with obsessive-compulsive disorder.
The above, is my summary of two points.
Although the fourth method in the code implementation section satisfies the second condition, the first condition cannot be satisfied. Well, next, I'll introduce a method, which I learned from the Internet, and the code is implemented as follows:
#include <iostream>using namespacestd;classsingleton{ Public: StaticSingleton *getinstance () {returnm_instance; } intgettest () {returnm_test; }Private: Singleton () {m_test=Ten; } StaticSingleton *m_instance; intm_test; //This is important classGC { Public : ~GC () {//We can destory all the resouce here, eg:db Connector, file handle and so on if(M_instance! =NULL) {cout<<"This is the test"<<Endl; Deletem_instance; M_instance=NULL; } } }; StaticGC GC;}; Singleton*singleton:: m_instance =NewSingleton (); Singleton:: GC Singleton:: GC;intMainintARGC,Char*argv []) {Singleton*singletonobj =Singleton:: getinstance (); cout<<singletonobj->gettest () <<Endl; return 0;}
At the end of the program run, the system calls the destructor of the static member GC of Singleton, which frees the resource, and the resource is released in a way that the programmer "does not know", and the programmer does not have to be particularly concerned, when using the code of the singleton pattern, Do not care about the release of resources. So what is the principle of this way of implementation? When I analyze the problem, I like to dissect it to the root of the problem and never get confused and stay on the surface. Because at the end of the program, all global variables are automatically refactored, and in fact, the system will also refactor static member variables for all classes, just as the static variables are global variables. We know that static variables and global variables in memory are stored in the static storage area, so they are treated equally when they are being refactored.
Since an internal GC class is used here, and the purpose of this class is to release resources, which is widely available in C + +, I'll summarize this technique in a later blog, see the RAII mechanism in C + +.
Pattern extension
In the actual project, a pattern will not be as simple as the code we have here, only in the proficiency of various design patterns, to better use in the actual project. The Singleton and factory models are often seen in real-world projects, and the combination of the two patterns is also common in projects. Therefore, it is necessary to summarize the combination of the two modes of use.
A product, which is produced in a factory, is a description of a factory model, and only one factory is required to produce a product, which is a description of a singleton pattern. So, in practice, a product, we only need a factory, at this time, we need a combination of Factory mode and singleton mode design. Since singleton mode provides a global access point to the outside world, we need to define an identity using the simple factory pattern, which identifies which single piece to create. Because of the more analog code, at the end of the article, provide download links.
Summarize
In order to write this article, I investigated a lot of information, because the information on the Internet in all aspects have a lot of flaws, quality participation is not homogeneous, I also caused a certain misleading. And this article, there is my own understanding, if there are errors, please correct me.
As a result of this article on the design pattern summary, I think than on-line 80% is comprehensive, I hope to be useful to everyone. In the actual development, and does not use the singleton pattern of so many, each design pattern, should be used in the most suitable occasions, in future projects, should be to have the ground, and not to use design patterns and use design patterns.
Reprint: HTTP://WWW.JELLYTHINK.COM/ARCHIVES/82
Singleton (singleton) mode and double-checked Locking (double check lock) mode