1, in the environment after GCC4.0:
#include <iostream>
using namespace Std;
Template <typename t>
class Singleton
{
Public:
Static t& getinstance () {
// The flaw in the use of local static variables is the uncertainty of creation and destruction. Because the singleton practice is created when the instance () function is accessed, a newly added access to the singleton somewhere will likely cause the singleton's lifetime to change. If it relies on other components, such as another singleton, then managing their lifetimes will be a disaster.
//It can even be said that it is better to use explicit instance lifetime management instead of Singleton. "
//Lock (); GCC 4.0 + compiler guarantees thread safety for internal static variables, which can be used without this sentence
//Why c++0x need to be added lock before, this is determined by the actual implementation of local static variables.
//In order to satisfy the requirement that local static variables be initialized only once
//, many compilers record information about whether the static variable has been initialized through a global flag bit.
//Then the pseudo code that initializes the static variable will look like this:
//bool flag = false;
if (!flag)
//{
//flag = true;
Staticvar = Initstatic ();
//}
Static T s;
//unlock ()
cout << "New S" << Endl;
return s;//If a pointer is returned that might have been deleted by an external caller, it would be more safe to return the reference here.
}
Private:
Singleton () {}
~singleton () {}
Singleton (const singleton& Other) {}
Singleton & operator = (const singleton& other) {}
};
Class Eager_singleton//a Hungry man mode
{
Private
Eager_singleton () {
}
~eager_singleton () {
}
Eager_singleton (const eager_singleton& Other);
eager_singleton& operator = (const eager_singleton& other);
Private
static Eager_singleton s; Initialization is performed by the main thread in a single-threaded manner before the main function is entered at the beginning of the program.
Public
Static eager_singleton& getinstance () {
return s;
}
};
Use Singleton as a component for other classes
Class Singletoninstance:public Singleton<singletoninstance> {
};
int main () {
Singleton<singleton>::getinstance ();
Eager_singleton::getinstance ();
return 0;
}
2, before the GCC4.0, without the lock implementation
Template<typename t>
Class Singleton:boost::noncopyable
{
Public
Static t& instance ()
{
Pthread_once (&ponce_, &singleton::init);
return *value_;
}
static void Init ()
{
Value_ = new T ();
}
Private
Static pthread_once_t Ponce_;
Static t* Value_;
};
Template<typename t>
pthread_once_t singleton<t>::p once_ = pthread_once_init;
Template<typename t>
t* singleton<t>::value_ = NULL;
3, before GCC4.0, with the lock implementation (transferred from the write pattern line to line of an interview scene)
Template <typename t>
Class Singleton
{
Public
Static t& Instance ()
{
if (m_pinstance = = NULL)
{
Lock lock;
if (m_pinstance = = NULL)
{
M_pinstance = new T ();
Destroy ();
}
return *m_pinstance;
}
return *m_pinstance;
}
Protected
Singleton (void) {}
~singleton (void) {}
Private
Singleton (const singleton& RHS) {}
singleton& operator = (const singleton& RHS) {}
void Destroy ()
{
if (m_pinstance! = NULL)
Delete m_pinstance;
M_pinstance = NULL;
}
static t* volatile m_pinstance;
};
Template <typename t>
t* singleton<t>::m_pinstance = NULL;
Because the call to the new operator is divided into allocating memory, calling the constructor, and assigning a value of three steps to the pointer, as in the following constructor call: "
1 singletoninstance pinstance = new Singletoninstance ();
"This line of code translates to the following form:"
1 Singletoninstance pheap = __new (sizeof (singletoninstance)); 2 pheap->singletoninstance::singletoninstance (); 3 Singletoninstance pinstance = pheap;
"This translates because the C + + standard specifies that if the memory allocation fails, or the constructor does not execute successfully, the new operator returns NULL. In general, the compiler does not easily adjust the order in which these three steps are executed, but when certain conditions are met, such as when the constructor does not throw an exception, the compiler may merge the first and third steps into the same step for optimization purposes: "
1 Singletoninstance pinstance = __new (sizeof (singletoninstance)); 2 Pinstance->singletoninstance:: Singletoninstance ();
"This may cause one of the threads to be switched to another thread after the memory allocation has been completed, and the second pass to Singleton will be passed over the IF branch because Pinstance has already been assigned, thus returning an incomplete object." Therefore, I added the volatile keyword to the static member pointer in this implementation. The actual meaning of the keyword is that the variable modified by it may be changed unexpectedly, so each time the variable it modifies requires its actual value to be taken from memory. It can be used to prevent the compiler from adjusting the order of instructions. It is only because the prohibit Reflow code provided by this keyword is assumed to be in a single-threaded environment, so it is not possible to prevent instructions from being re-routed in a multithreaded environment. ”
"Finally, my use of the atexit () keyword. When you create an instance of a type with the new keyword, we also register the function that freed the instance through the Atexit () function, guaranteeing that these instances can be properly refactored before the program exits. The properties of the function are also guaranteed to be first refactored after the created instance. In fact, the process of destructors for static type instances corresponds to the previous insertion of static initialization logic before the main () function is executed. ”
A reference or a pointer
"Now that you've used pointers in implementations, why do you still return references in the instance () function?" "The interviewer has thrown up a new question," he added.
"This is because the lifetime of the instance returned by Singleton is determined by the singleton itself, not the user code. We know that the most grammatical difference between pointers and references is that pointers can be null, and you can delete the instances that the pointers refer to by using the delete operator, but the reference is not possible. One of the semantic differences derived from this syntax distinction is the lifetime meaning of these instances: by referencing the returned instance, the lifetime is managed by non-user code, and the instance returned by the pointer may not be created at some point in time or can be deleted. But these two singleton are not satisfied, so here I use pointers instead of references. ”
"Are there other differences between pointers and references other than those you mentioned?" ”
Some The difference between pointers and references is mainly in several ways. From low to high level, there are differences in compiler implementations, grammatical and semantic. In terms of the compiler's implementation, declaring a reference does not allocate memory for the reference, but merely assigns an alias to the variable. The declaration of a pointer allocates memory. This difference in implementation leads to a number of grammatical differences: making a change to a reference causes its original point to be assigned a value, and making a change to the pointer will cause it to point to another instance, and the reference will always point to a type instance, causing it to not be null, and resulting in a number of grammatical differences due to that limitation. such as dynamic_cast does not match the behavior of references and pointers when they cannot be converted successfully. As far as semantics is concerned, the lifetime semantics mentioned earlier is a difference, and a function that returns a reference often guarantees that its return result is valid. In general, the root causes of semantic differences are often grammatical differences, so the semantic differences above are merely examples, and the real semantic differences often need to be considered in their context. ”
"You said in front of your multithreaded internal implementations use pointers, and return types are references. During writing, did you consider the case where the instance construction was unsuccessful, such as if the new operator failed to run? ”
Yes In the course of discussions with others, we have a different understanding of this problem. First, the construction of one instance might throw an exception in two places: the execution of the new operator and the exception thrown by the constructor. For the new operator, I would like to say a few points. For some operating systems, such as windows, they often use virtual addresses, so their operations are often not limited by the actual size of physical memory. For exceptions thrown in constructors, we have two strategies to choose from: Handling exceptions within constructors and handling exceptions outside of constructors. Handling exceptions within a constructor guarantees that the type instance is in a valid state, but is generally not the instance state we want. Such an instance would result in a more cumbersome use of it later, such as requiring more processing logic or causing the program to execute the exception again. Conversely, it is often better to handle exceptions outside of the constructor, because a software developer can change a range of variables to a legitimate state based on the state of the instance that was constructed when the exception was generated. For example, when we try to create a pair of interrelated type instances in a function, we should not maintain the state of the instance in the constructor when an exception is thrown by the constructor of an instance, because the previous instance is constructed in a way that the latter instance is created normally. In relative terms, discarding the latter instance and deleting the previous instance is a good choice. ”
I took a look at the whiteboard and continued: "We know that there are two very obvious flaws in the anomaly: efficiency, and pollution of the code." The use of exceptions in too small a granularity can result in an increase in the number of exceptions, which is harmful for efficiency and the neat type of code. Similarly, it is often necessary to use similar principles for composition of copy constructors. ”
"Conversely, the use of Singleton can also maintain this principle. Singleton is only a well-packaged global instance, it is also a good choice to maintain a normal state at a higher level if it is unsuccessful. ”
Anti-patten
"Since you mentioned that Singleton is just a packaged global variable, can you say it's the same and different from global variables?" ”
"A single piece can be said to be a substitute for global variables. It has many features of global variables: globally visible and throughout the application's lifecycle. In addition, the single-piece mode has properties that are not available for global variables: Only one object instance of the same type can have a function of lazy initialization (lazy), which avoids the problem of poor startup speed caused by time-consuming global variable initialization. To illustrate, the main purpose of Singleton is not to use as a global variable, but to ensure that the type instance has only one. The global access feature it has is just one of its side effects. But it is this side effect that makes it much more like a wrapped global variable, allowing parts of the code to manipulate it directly. Software developers need to be careful to read the code in which the sections manipulate them to understand how they are actually used, and not to get information such as component dependencies through the interface. If the singleton records the running state of the program, the state will be a global state. The timing of the invocation of the individual components to manipulate them becomes very important, and there is an implicit dependency between the individual components. ”
"Syntactically, first, the singleton pattern actually mixes the code of type functionality with the number of type instances, violating the SRP. The second singleton pattern creates a deterministic type in the instance () function, thereby prohibiting the possibility of providing another implementation through polymorphism. ”
"But from a system point of view, the use of Singleton is unavoidable: If a system has hundreds of services, then the delivery of them will become a disaster for the system." From many of the libraries offered by Microsoft, it often provides a way to get service functions, such as GetService (). Another way to mitigate the undesirable effects of the singleton pattern is to provide a small implementation of stateless or state-related implementations for the singleton mode. ”
In other words, Singleton itself is not a very bad pattern, the key to its use is when to use it and use it correctly. ”
Single-instance mode for C + + thread safety