1.1 Singleton (Single Instance)
If we think that a class only needs to generate one instance, we will design it as Singleton (single instance mode ).
Singleton is the most frequently used mode in our projects,This mode ensures that a class has only one instance and provides a global access point to it..
Singleton is an improved global variable. The description of this mode is simple, but the implementation is complicated. In particular, the life cycle management of Singleton objects is the biggest headache for Singleton.
This article will discuss the following topics:
L compared with simple global objects, Singleton features
L basic C ++ methods used to support a single instance
L resource leakage and Meyers Singleton
L dead reference
L Phoenix Singleton
L Singleton with lifetime
L Multithreading
L use Singleton in a dynamic library
1.1.1 static data + static functions! = Singleton
Does Singleton seem to be replaced by static member variables + static member functions?
For example:
Class font {...};
Class printport {...};
Class printjob {...};
Class myonlyprinter
{
Public:
Static void addprintjob (printjob & newjob)
{
If (printqueue _. Empty () & printingport _. Available ())
{
Printingport _. Send (newjob. Data ());
}
Else
{
Printqueue _. Push (newjob );
}
}
PRIVATE:
Static queue <printjob> printqueue _;
Static printerport printingport _;
Static font defaultfont _;
}
Printjob someprintjob(“mydocument.txt ");
Myonlyprinter: addprintjob (someprintjob );
What are the disadvantages of the above Code?
I think there are two main disadvantages:
N code initialization and cleanup may be troublesome.
N Initialization is completed when the program is started. If the static function is not called when the program exits, the initialization is done in vain.
1.1.2 some basic C ++ methods used to support Singleton
Implementation Code of the original version of a single instance:
Class Singleton
{
Public:
Static Singleton * instance (); // single-instance operation
PRIVATE:
Singleton (); // close the default constructor
Singleton (const Singleton &); // close the copy constructor
Singleton & operator = (const Singleton &); // close the value assignment operator
~ Singleton (); // avoid external Delete
Static Singleton * m_instance; // Singleton pointer
};
// Create a singleton pointer and initialize it
Singleton * singleton: m_instance = NULL;
// Single-instance operation implementation
Singleton * singleton: instance ()
{
If (null = m_instance)
{
M_instance = new Singleton ();
}
Return m_instance;
}
// Constructor
Singleton: Singleton ()
{
}
// Destructor
Singleton ::~ Singleton ()
{
}
Through the code above, the uniqueness of the singleton object has been realized in the compilation period, which is the essence of implementing the singleton Design Pattern in C ++.
In addition to using the new operator to dynamically allocate memory to implement Singleton, we often use static variable instances to implement this mode. Below we provide a code to implement Singleton using static variables.
Class Singleton
{
Public:
Static Singleton & instance (); // single-instance operation
PRIVATE:
Singleton (); // close the default constructor
Singleton (const Singleton &); // close the copy constructor
Singleton & operator = (const Singleton &); // close the value assignment operator
~ Singleton (); // prevent external Delete PRIVATE:
Static Singleton m_instance; // static Singleton variable
};
// Create a singleton
Singleton singleton: m_instance;
// Single-instance operation implementation
Singleton & singleton: instance ()
{
Return m_instance;
}
Because it is a static variable of the class, the above Code instantiated the singleton object at the beginning of the process.
There are two disadvantages:
N Initialization is completed when the program is started. If the static function is not called when the program exits, the initialization is done in vain;
N initialization order cannot be determined, which may cause problems, such:
# Include "singleton. h"
Int global = singleton: instance ()-> dosomething ();
The compiler cannot guarantee that m_instance is initialized before Global global variables, so an exception may occur when the program starts.
Therefore, although the above implementation is simple, we recommend that you resist this temptation.
1.1.3 Meyers Singleton
We have not discussed the pointer implementation method discussed in the previous section, that is, when to analyze the structure of the singleton object: When should the singleton object destroy its own entity? This topic is not discussed in the gof book. In fact, this issue is really tricky.
In fact, even if Singleton is not deleted, it will not cause memory leakage.. In addition, when a process is terminated, all modern operating systems can completely release the memory used by the process.
However, leakage may still exist, and it is more concealed and harmful, that is, resource leakage. This is because the singleton constructor can search for a wide range of resources: network connections, OS kernel objects, various handles in the IPC method, and database connections.
The only correct way to avoid resource leakage is to delete the singleton object when the program is closed.
There is an improved version of the static variable initialization method, which can destroy the singleton object by leveraging the peculiar compiler techniques.
Singleton & singleton: instance ()
{
Static Singleton m_instance; // static Singleton variable
Return m_instance;
}
Scott Meyers(Meyers 1996a, Clause 26)It is first proposed, so it is called meryars Singleton.The static object declared in the function. It is initialized when the function is called for the first time..
The compiler generates some code to ensure that the Destructor will be called before the process exits. In fact, the atexit function will be called in the code generated by the compiler, and the single-instance destructor will be registered in atexit to ensure that the Destructor will be called before the process exits.
The atexit function maintains a stack and runs it after the function pointer is pushed to the stack.
We can also call atexit in the Code as shown in the following code.
Class Singleton
{
...
PRIVATE:
Static void freeinstance (void); // release the singleton Variable Function
};
// Single-instance operation implementation
Singleton * singleton: instance ()
{
If (null = m_instance)
{
M_instance = new Singleton ();
// Use the system function atexit to tell the system to call freeinstance when the program exits.
Atexit (freeinstance );
}
Return m_instance;
}
// Release the singleton Variable Function
Void singleton: freeinstance (void)
{
If (null! = M_instance)
{
Delete m_instance;
M_instance = NULL;
}
}
1.1.4 dead reference
Meryers Singleton is a good Singleton implementation method. In most cases, it is enough to implement Singleton using this method, but in some cases there will still be problems.
For example, assume that a program uses three singletons: keyboard, display, and log. We use meryers Singleton to implement these three singletons. Assume that keyboard and display are instantiated before log, log should be first parsed when the process Exits according to the principle of first-in-first-out after atexit. If the log structure is complete, an error occurred when calling the keyboard destructor. In this case, you need to call the log to record the log, but the log has been destructed, so the program will crash. This is the dead reference problem.
First, let's look at a simple solution:
// Singleton. h
Class Singleton
{
Public:
Singleton & instance ()
{
If (! Pinstance _)
{
// Check for dead reference
If (Destroyed _)
{
Ondeadreference ();
}
Else
{
// First call-Initialize
Create ();
}
}
Return pinstance _;
}
PRIVATE:
// Create a new singleton and store
// Pointer to it in pinstance _
Static void create ();
{
// Task: Initialize pinstance _
Static Singleton theinstance;
Pinstance _ = & theinstance;
}
// Gets called if dead reference Detected
Static void ondeadreference ()
{
Throw STD: runtime_error ("Dead reference detected ");
}
Virtual ~ Singleton ()
{
Pinstance _ = 0;
Destroyed _ = true;
}
// Data
Singleton pinstance _;
Bool destroyed _;
... Disabled 'tors/operator =...
};
// Singleton. cpp
Singleton * singleton: pinstance _ = 0;
Bool singleton: destroyed _ = false;
When the dead reference problem occurs, the program will directly throw a C ++ exception. This solution is cheap, simple, and does not lose efficiency.
1.1.5 Phoenix Singleton
Although the solution in the previous section eliminates uncertain behavior, this solution is not satisfactory.
Modern C ++ design first tries to solve this problem through Phoenix Singleton. Phoenix is the so-called Phoenix that can be reborn from its own ashes. If you can design logs into Singleton of this type, you can solve the problem.
Class Singleton
{
... As before...
Void killphoenixsingleton (); // added
};
Void singleton: ondeadreference ()
{
// Obtain the shell of the destroyed Singleton
Create ();
// Now pinstance _ points to the "Ashes" of the singleton
//-The raw memory that the Singleton was seated in.
// Create a new Singleton at that address
New (pinstance _) Singleton;
// Queue this new object's destruction
Atexit (killphoenixsingleton );
// Reset destroyed _ because we're back in business
Destroyed _ = false;
}
Void singleton: killphoenixsingleton ()
{
// Make all ashes again
//-Call the Destructor by hand.
// It will set pinstance _ to zero and destroyed _ to true
Pinstance _-> ~ Singleton ();
}
It seems that the code is okay, but it is not actually successful. The cause of the failure is a C ++ standard vulnerability. This vulnerability means that when a program calls the registered function in the atexit stack, this function itself cannot call the atexit function, otherwise it will have unpredictable consequences (resource leakage is lighter, program crash ).
1.1.6 implement "Singletons with life"
For the dead reference problem, modern c ++ design provides the second solution: singletons with a life cycle.
If you can set the lifetime of Singleton in the program, ensure that the lifetime of log is longer than that of display and keyboard. Similar to the following code:
// This is a singleton class
Class somesingleton {...};
// This is a regular class
Class someclass {...};
Someclass * pglobalobject (New someclass );
Int main ()
{
Setlongevity (& somesingleton (). instance (), 5 );
// Ensure pglobalobject will be deleted
// After somesingleton's instance
Setlongevity (pglobalobject, 6 );
...
}
The implementation of Singleton with a lifetime is somewhat complicated. The implementation code is as follows:
Namespace private
{
Class lifetimetracker
{
Public:
Lifetimetracker (unsigned int X): longevity _ (x ){}
Virtual ~ Lifetimetracker () = 0;
Friend inline bool compare (
Unsigned int longevity,
Const lifetimetracker * P)
{Return p-> longevity _> longevity ;}
PRIVATE:
Unsigned int longevity _;
};
// Definition required
Inline lifetimetracker ::~ Lifetimetracker (){}
}
Namespace private
{
Typedef lifetimetracker ** trackerarray;
Extern trackerarray ptrackerarray;
Extern unsigned int elements;
}
// Helper destroyer Function
Template <typename T>
Struct deleter
{
Static void Delete (T * pobj)
{Delete pobj ;}
};
// Concrete lifetime tracker for objects of type T
Template <typename T, typename destroyer>
Class concretelifetimetracker: Public lifetimetracker
{
Public:
Concretelifetimetracker (T * P, unsigned int longevity, Destroyer D)
: Lifetimetracker (longevity ),
, Ptracked _ (p)
, Destroyer _ (D)
{}
~ Concretelifetimetracker ()
{
Destroyer _ (ptracked _);
}
PRIVATE:
T * ptracked _;
Destroyer destroyer _;
};
Void atexitfn (); // Declaration needed below
}
Template <typename T, typename destroyer>
Void setlongevity (T * pdynobject, unsigned int longevity,
Destroyer d = private: deleter <t>: delete)
{
Trackerarray pnewarray = static_cast <trackerarray> (
STD: realloc (ptrackerarray, sizeof (t) * (elements + 1 )));
If (! Pnewarray) Throw STD: bad_alloc ();
Ptrackerarray = pnewarray;
Lifetimetracker * P = new concretelifetimetracker <t, Destroyer> (
Pdynobject, longevity, d );
Trackerarray Pos = STD: upper_bound (
Ptrackerarray, ptrackerarray + elements, longevity, compare );
STD: copy_backward (Pos, ptrackerarray + elements,
Ptrackerarray + elements + 1 );
* Pos = P;
++ Elements;
STD: atexit (atexitfn );
}
Static void atexitfn ()
{
Assert (elements> 0 & ptrackerarray! = 0 );
// Pick the element at the top of the stack
Lifetimetracker * pTop = ptrackerarray [elements-1];
// Remove that object off the stack
// Don't check errors-realloc with less memory
// Can't fail
Ptrackerarray = static_cast <trackerarray> (STD: realloc (
Ptrackerarray, sizeof (t) * -- elements ));
// Destroy the element
Delete pTop;
}
1.1.7 Multithreading
The preceding implementation code has not considered multithreading. In most cases, the program runs in a multi-threaded environment.
The code for locking Singleton can be as simple as the following:
Singleton & singleton: instance ()
{
// Mutex _ Is A mutex object
// Lock manages the mutex
Lock guard (mutex _);
If (! Pinstance _)
{
Pinstance _ = new Singleton;
}
Return * pinstance _;
}
This is easy to use, but inefficient.
Use the so-calledDual detection lock technologyEfficiency issues can be solved:
Singleton & singleton: instance ()
{
If (! Pinstance _) // 1
{
Guard myguard (Lock _); // 2
If (! Pinstance _) // 3
{
Pinstance _ = new Singleton; // 4
}
}
Return * pinstance _;
}
Even if the dual-check lock technology is used, the Code is not always correct in practice. Sometimes the compiler will optimize the above Code to change the code execution sequence, leading to lock failure.
To avoid unnecessary optimization by the compiler, you must add the volatile modifier before the pinstance _ declaration.
1.1.8 use Singleton in a dynamic library
The use of Singleton in a dynamic library is related to the lifecycle of static variables in the dynamic library. After practice, we can draw a conclusion, the Singleton technology we discussed earlier runs in a dynamic library.
When the dynamic library is detached, the pointer stored in the atexit stack is called to ensure that the Destructor is executed.