Index
- Static mode is not Singleton Mode
- Hunger Mode
- Lazy mode (Stack-rough version)
- Lazy mode (local static variables-best version)
- Sample Code and precautions (optimal implementation)
- Additional reading
- References
I strongly agree that the rational use of the design pattern can make the code easier to understand and maintain. However, I myself seldom use the Singleton pattern.
It is shameful that I did not find room for improvement in singleton mode until I read c ++ in theory: The Singleton pattern and Part I some time ago.
Author J. Nakamura lists three methods of Singleton mode in the log class:
// log.h#ifndef __LOG_H#define __LOG_H#include <list>#include <string>class Log {public: virtual void Write(char const *logline); virtual bool SaveTo(char const *filename);private: std::list<std::string> m_data;};#endif // __LOG_HStatic mode is not Singleton Mode
The mistake that a beginner may make is to use all the member variables and member methods.StaticAfter modification, it is the singleton mode:
class Log {public: static void Write(char const *logline); static bool SaveTo(char const *filename);private: static std::list<std::string> m_data;};In log.cpp we need to addstd::list<std::string> Log::m_data;
At first glance, it does have many conditions for the singleton mode, but it also has some problems. first, the initialization sequence of static member variables does not depend on the constructor. It depends on the mood of the compiler, and the initialization sequence cannot be guaranteed (in extreme cases: YesA BTwo member objects,BYou needAAs an initialization parameter, your class isRequiredMust have constructors and ensure the initialization order ).
Second, the most serious problem is the loss of an important feature facing objects-"polymorphism", which is impossible for static member MethodsVirtual.LogSub-classes of a class cannot enjoy the convenience of "polymorphism.
Hunger Mode
Hunger ModeA singleton instance is initialized immediately when the program is running:
class Log {public: static Log* Instance() { return &m_pInstance; } virtual void Write(char const *logline); virtual bool SaveTo(char const *filename);private: Log(); // ctor is hidden Log(Log const&); // copy ctor is hidden static Log m_pInstance; static std::list<std::string> m_data;};// in log.cpp we have to addLog Log::m_pInstance;
The problem with this mode is also obvious. Classes are now polymorphism, but the initialization sequence of static member variables is not guaranteed.
There is another problem (I have encountered real events before, and I have been using the "lazy model" mentioned below): There are two Singleton classes.AsingletonAndBsingletonOne day you wantBsingletonIn the constructorAsingletonInstance.BsingletonM_pinstance static object may firstAsingletonOne-step call of the initialization constructor, resultAsingleton: instance ()The returned result is an uninitialized memory area, and the program crashes directly before it runs.
Lazy mode (Stack-rough version)
J. nakamura calls it "gamma Singleton" because it is the method that gamma uses in his famous book <Design Patterns> (<Design Patterns>) [gamma. it is called "lazy mode" because a singleton instance is initialized only when it is used for the first time:
class Log {public: static Log* Instance() { if (!m_pInstance) m_pInstance = new Log; return m_pInstance; } virtual void Write(char const *logline); virtual bool SaveTo(char const *filename);private: Log(); // ctor is hidden Log(Log const&); // copy ctor is hidden static Log* m_pInstance; static std::list<std::string> m_data;};// in log.cpp we have to addLog* Log::m_pInstance = NULL;
Instance ()Only when it is called for the first timeM_pinstanceAllocate memory and initialize it. Well, it seems that all the problems have been solved, the initialization sequence is ensured, and there is no problem with polymorphism.
However, you may have discovered a problem. When the program exits, the Destructor is not executed. this may cause resource leakage in some systems with unreliable design, such as file handles, socket connections, and memory. fortunately, common systems such as Linux, Windows 2000, and XP can automatically release system resources when the program exits. however, this may still be a hidden risk, at least J. in Nakamura's impression, some systems will not be automatically released.
The solution to this problem is to addDestructor ()Method:
virtual bool destructor() { // ... release resource if (NULL!= m_pInstance) { delete m_pInstance; m_pInstance = NULL; }}
Then, when the program exits, make sure thatDestructor ()This method is reliable, but tedious. Fortunately, Master Meyers has a simpler method.
Lazy mode (local static variables-best version)
It is also called Meyers Singleton [Meyers]:
class Log {public: static Log& Instance() { static Log theLog; return theLog; } virtual void Write(char const *logline); virtual bool SaveTo(char const *filename);private: Log(); // ctor is hidden Log(Log const&); // copy ctor is hidden Log& operator=(Log const&); // assign op is hidden static std::list<std::string> m_data;};
InInstance ()The benefit of defining local static variables in a function is that,The thelog constructor only calls "instance ()" for the first time ()Is initialized to achieve the same dynamic initialization effect as the "Stack version", ensuring the initialization sequence of member variables and Singleton.
It also has a potential security measure,Instance ()The returned result is a reference to a local static variable. If the returned result is a pointer,Instance ()The caller may mistakenly think that he wants to check the validity of the pointer and destroy the constructor. The constructor and the copy constructor are also private, so that the user of the class cannot instantiate it on his own.
In addition, the structure order of multiple Singleton instances is different from that of the singleton instances.
Sample Code and precautions (optimal implementation)
In the following C ++ code snippetSingletonReplace it with the actual class name to quickly obtain a singleton class:
class Singleton {public: static Singleton& Instance() { static Singleton theSingleton; return theSingleton; } /* more (non-static) functions here */private: Singleton(); // ctor hidden Singleton(Singleton const&); // copy ctor hidden Singleton& operator=(Singleton const&); // assign op. hidden ~Singleton(); // dtor hidden};
Note
The constructor of any two Singleton classes cannot reference each other's instances. Otherwise, the program will crash. For example:
ASingleton& ASingleton::Instance() { const BSingleton& b = BSingleton::Instance(); static ASingleton theSingleton; return theSingleton;}BSingleton& BSingleton::Instance() { const ASingleton & b = ASingleton::Instance(); static BSingleton theSingleton; return theSingleton;}
Be careful when using multiple threads. if a unique instance has not been created, two threads call the creation method at the same time, and neither of them detect the existence of a unique instance, one instance is created at the same time, in this way, two instances are constructed, which violates the unique instance principle in the singleton mode. the solution to this problem is to provide a mutex lock for the variables that indicate whether the class has been instantiated (although this will reduce the efficiency ).
When multiple Singleton instances reference each other, You need to carefully process the destructor. For example, the initialization sequence isAsingleton»Bsingleton»CsingletonThree Singleton classes, of whichAsingleton BsingletonThe destructor of is called.CsingletonMember functions of the instance. When the program exits,Csingleton destructorWill be called first, resulting in invalid instance, then subsequentAsingleton BsingletonWill fail, causing the program to exit unexpectedly.
Additional reading
- Anti-pattern: the obvious but inefficient design pattern or the design pattern to be optimized in practice is a common bad method to solve the problem. they have been researched and categorized to prevent future repetition and can be identified when developing systems that are not yet put into operation. (Some of the anti-patterns are quite interesting );
- C ++ in theory: The Singleton pattern, Part 2: the second part of the "C ++ in theory" series. The main content is the singleton mode in generic programming, I am not very familiar with generics. If you are interested, you can check it out.
References
| [Gamma] |
Design Patterns: E. Gamma, R. Helm, R. Johnson and J. vlissides. |
| [Meyers] |
More effective tive C ++: S. Meyers. |