Implementation of the singleton pattern

1. Scenarios used by the singleton pattern

In a computer, there are many devices such as printers. When printing a document, only one printer is needed, and as long as there is one printer, the printing of the document can be completed. On the contrary, if there are multiple printers in the system working at the same time, resources will be wasted, and the output file may be incomplete, which is prone to problems.

In the same way, in a class, we only need one instance to complete its work, which is where the singleton pattern is needed.

2. Requirements for Singleton Pattern Implementation

To realize the singleton pattern, complete three points (1) only one instance can be generated, (2) this instance must be implemented by this class; (3) this instance must allow the system to see itself; to achieve this purpose, we need to do three requirements:

(1) The constructor is private. If the constructor is made private, it is not allowed to create other objects (instances);

(2) The class definition contains a static private object;

(3) Provide a static public function to obtain this object;

3. C++ implements the singleton pattern

(1) Lazy man style

#include <iostream>
using namespace std;

class CSingleton
{
public:
	static CSingleton* GetInstance()
	{
		if (pInstance == NULL) //Determine whether it is called for the first time
		{
			pInstance = new CSingleton;
		}
		return pInstance;
	}
	void ReleaseInstance()
	{
		delete this;
	}
private:
	CSingleton ()
	{}
	CSingleton(const CSingleton& instance);
	~ CSingleton ()
	{
		pInstance = NULL;
	}
private:
	static CSingleton *pInstance;
};

There is a big problem with the above method. It can only be used for a single thread. If it is multi-threaded, this program is meaningless, because two threads may execute the object creation function at the same time. At this time, it is equivalent to creating two instance.

(2) We can use locking to implement multi-threaded singleton mode

The lock of C++11 is defined in the header file <mutex>, which contains four different mutexes.

  • Mutex: Provides the core functions lock() and unlock(), as well as the try_lock() method of the non-blocking method, which returns immediately once the mutex is unavailable.
  • Recursive_mutex: Allows multiple requests for a mutex in the same thread.
  • Timed_mutex: Similar to the mutex above, but it has two other methods, try_lock_for() and try_lock_until(), which are used to acquire the mutex during a certain period of time or between the arrival of a certain moment.
  • Recursive_timed_mutex: Combines the use of timed_mutex and recuseive_mutex.

We use the lock() function and unlock() function to implement the code:

#include <iostream>
using namespace std;
#include <mutex>

mutex g_lock;
class CSingleton
{
public:
	static CSingleton* GetInstance()
	{
		if (pInstance == NULL)
		{
			g_lock.lock();
			if (pInstance == NULL) //Determine whether it is called for the first time
			{
				pInstance = new CSingleton;
			}
			g_lock.unlock();
			
		}
		return pInstance;
		
	}
	void ReleaseInstance()
	{
		delete this;
	}
private:
	CSingleton ()
	{}
	CSingleton(const CSingleton& instance);
	~ CSingleton ()
	{
		pInstance = NULL;
	}
private:
	static CSingleton *pInstance;
};

In this way, only when the pInstance is empty, that is, when it is not created, the locking operation is required. When the pInstance is created, the locking operation is not required, because the efficiency of the locking operation is very slow and consumes resources. Doing so not only enables multi-threaded operations, but also improves efficiency.


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324886223&siteId=291194637