Singleton class in C + + interview

Source: Internet
Author: User

Intro

"Please write a singleton." The interviewer smiled and told me.

"It's really simple. "I thought about it and wrote down the following singleton implementation on the Whiteboard:

1ClassSingleton2{ 3 public: 4 static singleton& Instance ()  5  { 6 static Singleton Singleton;  7 return singleton;< Span style= "color: #008080;" > 8 } 9 10 private:11  Singleton () { }; 12};             

"Then please explain the composition of the implementation." "The interviewer still wears a smile on his face."

"The first thing to say is the Singleton constructor. Since singleton restricts its type instances to have only one, we should ensure that they are not created arbitrarily by user code by setting the constructor to non-public. In the type instance access function, we have only one requirement for the instance through local static variables. In addition, with this static variable, we can delay the creation of the instance until the instance access function is called, to increase the program's startup speed. ”

Protection

"It's good to say, and what's even more valuable is that you can notice that the constructor is protected. After all, the middleware code needs to be very rigorous to prevent user code misuse. So, what components do we need to protect besides the constructors? ”

"There are also copy constructors, destructors, and assignment operators that need to be protected. Perhaps, we also need to consider the accessor operator. This is because the compiler creates a default implementation for these members when needed. ”

"Then can you elaborate on the circumstances under which the compiler will create the default implementation and why these default implementations are created?" The interviewer went on asking.

"In cases where these members are not declared, the compiler will use a series of default behaviors: the construction of the instance is to allocate a portion of the memory, not to do anything in that part of the memory; the copy of the instance is simply a bitwise copy of the memory in the original instance to the new instance. , and the assignment operator is also a copy of the information that is owned by the type instance. In some cases, these default behaviors no longer satisfy the criteria, and the compiler will attempt to create the default implementations of those members based on the information that is already available. These factors can be divided into several types: the corresponding member provided by the type, the virtual function in the type, and the virtual base class of the type. ”

As an example of a constructor, if a member or base class of the current type provides a user-defined constructor, the memory copy alone may not be the correct behavior. This is because the member's constructor may contain many execution logic such as member initialization, member function invocation, and so on. At this point the compiler needs to generate a default constructor for this type to perform a call to the member or base class constructor. Also, if a type declares a virtual function, the compiler still needs to generate a constructor to initialize a pointer to the virtual function table. If you have a virtual base class in each derived class of a type, the compiler also needs to generate a constructor to initialize the location of the virtual base class. These conditions also need to be considered in the copy constructor: If a member variable of a type has a copy constructor, or if its base class has a copy constructor, the bit copy will no longer satisfy the requirement because some logic that is not a bit copy may be executed within the copy constructor. Also, if a type declares a virtual function, the copy constructor needs to initialize the virtual function table pointer based on the target type. If a base class instance is copied, its virtual function table pointer should not point to the virtual function table of the derived class. Similarly, if you have a virtual derivation in each derived class of a type, the compiler should also generate a copy constructor for it to correctly set the offsets for each virtual base class. ”

"Of course, the destructor is slightly simpler: simply call the destructor of its members and the destructor of the base class, without having to consider the setting of the virtual base class offset and the setting of the virtual function table pointer." ”

"In these default implementations, the individual native type members of the type instance are not given the opportunity to initialize. But this is generally considered the responsibility of the software developer, not the compiler. "After all this, I took a breath, and I was secretly thankful that I had studied that part of the content.

"You just mentioned the need to consider the Protect access operator, right?" I want to know. ”

"Good." The first thing to declare is that almost everyone would assume that overloading of the fetch operator is evil. It is even said that boost provides the AddressOf () function to prevent errors caused by this behavior. On the other hand, we need to discuss why users use the accessor operator. Often a reference is returned by Singleton, and the reference is given a pointer of the appropriate type. Syntactically, the biggest difference between a reference and a pointer is whether it can be deleted by the DELETE keyword and whether it can be null. However, Singleton returns a reference that means that its lifetime is managed by non-user code. Therefore, it is obviously a user error to use the FETCH operator to get the pointer and then delete the instance returned by singleton with the DELETE keyword. In summary, it doesn't make much sense to set the accessor operator to private. ”

Reuse

"OK, now let's change the subject." If I have several types that need to be implemented as Singleton, how do I use the code you've written? ”

Just still complacent I suddenly realized: this singleton implementation is not reusable. No way, I had to say: "In general, the more popular method of reuse a total of three kinds: combination, derivation and template. The first thing you can think of is that the reuse of Singleton is only a reuse of the instance () function, so it is a good choice to derive from Singleton to inherit the function's implementation. The instance () function is better if it can change the return type based on the actual type. So singular recursive templates (crtp,the Curiously recurring template pattern pattern) are a very good choice. "So I quickly wrote down the following code on the whiteboard:

1 Template <typename t>2ClassSingleton3{4Public:5Static t&Instance ()6{7StaticT s_instance;8ReturnS_instance;9}10 11 protected:12 Singleton (void13 ~singleton (void14 15 private:16 Singleton (const singleton& RHS) {}17 singleton& operator = (const singleton& RHS) {}18};     

I also wrote on the whiteboard how to reuse the singleton implementation:

Public Singleton<singletoninstance>, .....

"When the singleton implementation needs to be reused, we just need to derive from Singleton and set the singleton generic parameter to that type." ”

Life-time Management

"I see you have used static variables in the implementation, can you describe some of the characteristics of the lifetime in the singleton implementation above?" After all, life-time management is also an important topic in programming. The interviewer raised the next question.

"Well, let me think about it." I think the discussion of the Singleton's lifetime characteristics needs to be divided into two areas: the lifetime of the static variable used in the singleton and the lifetime shown in the singleton external user code. The static variable used in Singleton is a local static variable, so it is created only when the singleton Instance () function is called, thus having the effect of lazy initialization (lazy), which improves the startup performance of the program. At the same time, the instance will survive until the program finishes executing. As far as the singleton user code is concerned, its lifetime runs through the entire program life cycle, starting from the start of the program until the execution of the program is complete. Of course, a flaw in Singleton's lifetime 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. ”

"Well, you can refer to the case where the construction of a single piece and the uncertainty of the destructor sequence can lead to a fatal error when the program is initialized and closed. It can be said that this is an important disadvantage of realizing singleton by local static variables. Do you have a way to solve the problem of lifetime management caused by the correlation between multiple singleton you mentioned? ”

I suddenly realized that I had a problem with myself: "Yes, we can change the implementation of Singleton to use global static variables and sort these global static variables in a specific order in the file." ”

However, static variables will be initialized using eager initialization, which may have a significant performance impact. In fact, what I want to hear you say is that for two singleton with associations, the code that uses them is often confined to the same region. One solution to this problem is often the implementation of the management logic used for them as Singleton, and the explicit lifetime management of them in internal logic. But don't worry, because the answer is too much of a word of thumb. So next question, since you mentioned that global static variables can solve this problem, can you explain the life cycle of global static variables? ”

The compiler inserts a piece of code before the program's main () function executes to initialize the global variable. Of course, static variables are also included. This process is called static initialization. ”

"Well, very well. Implementing singleton with global static variables does have a certain impact on performance. But have you noticed that it also has some advantages? ”

See me for a long time did not answer, the interviewer volunteered to help me to solve: "Is thread security." Since user code has not yet been executed at static initialization, it is often in a single-threaded environment, guaranteeing that Singleton really has only one instance. Of course, this is not a good solution. So, let's talk about the multi-threaded implementation of Singleton. ”

Multithreading

"First please write a thread-safe singleton implementation. ”

I took the pen, on the whiteboard written on the long-known heart of multi-threaded security implementation:

1 Template <typename t>2ClassSingleton3{4Public:5Static t&Instance ()6{7if (m_pinstance = =NULL)8{9 LockLock;10if (m_pinstance = =NULL)11{M_pinstance =NewT ();13Atexit (Destroy);14}15return *M_pinstance;16}17return *M_pinstance;18}1920Protected:Singleton (void) {}~singleton (void) {}2324Private:Singleton (Const singleton&RHS) {}singleton&operator = (Const singleton&RHS) {}2728voidDestroy ()29  {30 if (m_pinstance!=31  delete m_pinstance; 32 m_pinstance = Null;}34 35 static t*  M_pinstance;36 }; 37 38 template <typename t>39 t* singleton<t>::m_pinstance = NULL;           

"It was a wonderful writing. Can you explain this singleton implementation on a row-by-line basis? ”

"Good." First, I used a pointer to record the created singleton instance, not the local static variable. This is because local static variables can cause problems in a multithreaded environment. ”

"I would like to insert a sentence, why local static variables in the multi-threaded environment problems?" ”

This is determined by the actual implementation of the local static variable. In order to satisfy the requirement that local static variables be initialized only once, many compilers record the information that the static variable has been initialized through a global flag bit. Then, the pseudo code that initializes the static variable will look like this: ".

False;  if (!  Flag){true;  5 Staticvar = initstatic ();  6}        

"Then after the first thread performs a check on the flag and enters the If branch, the second thread may be started and thus into the if branch." This way, two threads will perform the initialization of the static variable. So here I use a pointer and use a lock to guarantee that only one thread can initialize the pointer at the same time before assigning a pointer to it. Also based on performance considerations, we need to check that the pointer has been initialized before each access to the instance to avoid the need to request control of the lock for each access to Singleton. ”

"At the same time," I swallowed, "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, just like the following constructor call:"

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. ”

The interviewer lifted his hands and looked at the time: "Well, the time has come." Your C + + skills are already very good. I believe that we will become colleagues in the near future. ”

The author notes: This is writing Patterns line by the line of an article, but finally think, write mode of people too many, I still save it ...

The next article returns to WPF, and the environment is just right. There may be other things in between, such as HTML5,JS, security, and so on.

The first time to write essays, do not know the effect is not good. Because this article is characterized by scattered knowledge points, and hidden in every word of the article ... The advantage is easy to write, hehe ...

Reprint please specify the original address: http://www.cnblogs.com/loveis715/archive/2012/07/18/2598409.html

Commercial reprint please contact me in advance:[email protected], I will only ask to add the author name and blog Home link.

Singleton class in C + + interview

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.