[C++] Realize the Single Case Model [Lazy Man Model, Hungry Man Model]

Posted by the_damo2004 on Fri, 06 Sep 2019 09:57:05 +0200

Singleton pattern: Ensure that a class is instantiated only once! There are two models of singleton: lazy man and hungry man.

Lazy Man Mode: The first time a class is used, it is de-instantiated; it is suitable for use when the number of visits is small;

Time for space, lazy mode itself is thread insecure! ]

Hunger mode: Class is initialized when it is defined; because thread synchronization is required, it is used when there are more threads to be accessed or when there are more threads to be accessed.

Space for time, safety! ]

"Hungry Han Model"

template<class T>
class singleton
{
protected:
	sigleton {};  //Constructor
private:
	sigleton(const sigleton&) {}; //No Copying
	sigleton& operator = (const sigleton&) {}; //Prohibit assignment
	static T* m_instance;
public:
	static T* GetInstance();
};
template<class T>
T* sigleton<T>::GetInstance()
{
	return m_instance;
}
template<class T>
T* sigleton<T>::m_instance = new T();

When instantiating the m_instance variable, the constructor of the class is called directly, and the m_instance has been assigned when the variable is not used; in the case of multi-threading, it must be thread-safe, because there is no problem of multi-threading instantiation;

The Lazy Man Model

template<class T>
class singleton
{
protected:
	sigleton {};  //Constructor
private:
	sigleton(const sigleton&) {}; //No Copying
	sigleton& operator = (const sigleton&) {}; //Prohibit assignment
	static T* m_instance;
public:
	static T* GetInstance();
};
template<class T>
T* sigleton<T>::GetInstance()
{
	if(m_instance == NULL)
	{
		m_instance = new T();
	}
	return m_instance;
}
template<class T>
T* sigleton<T>::m_instance = NULL;

In lazy mode, defining m_instance variable is equal to NULL first. When calling GetInstance() method, judging whether to assign value is unsafe, because when multiple threads call GetInstance() method at the same time, it may lead to multiple instances, so in order to achieve thread safety, it must be locked.

Improvement 1 uses mutex to achieve thread security, but it affects efficiency

template<class T>
class singleton
{
protected:
	sigleton {};  //Constructor
private:
	sigleton(const sigleton&) {}; //No Copying
	sigleton& operator = (const sigleton&) {}; //Prohibit assignment
	static T* m_instance;
public:
	static T* GetInstance();
};
template<class T>
T* sigleton<T>::GetInstance()
{
	pthread_mutex_lock(&mutex);
	if(m_instance == NULL)
	{
		m_instance = new T();
	}
	pthread_mutex_unlock(&mutex);
	return m_instance;
}
template<class T>
                                   //Macro static initialization mutex
pthread_murex_t sigleton<T>::mutex = PTHREAD_MUTEX_INITIALIZER;
template<class T>
T* sigleton<T>::m_instance = NULL;

Lock every time GetInstance() method is called in, which affects efficiency.

Improving 2 > "Double Check Lock" Mechanism and Transition with Local Variables

template<class T>
class singleton
{
protected:
	sigleton {};  //Constructor
private:
	sigleton(const sigleton&) {}; //No Copying
	sigleton& operator = (const sigleton&) {}; //Prohibit assignment
	static T* m_instance;
public:
	static T* GetInstance();
};
template<class T>
T* sigleton<T>::GetInstance()
{
	if(m_instance == NULL)
	{
		pthread_mutex_lock(&mutex);
		if(m_instance == NULL)
		{
			m_instance = new T();
		}
		pthread_mutex_unlock(&mutex);
	}
	return m_instance;
}
template<class T>
T* sigleton<T>::m_instance = NULL;

Double check lock mechanism, but when m_instance = new T(); class T may not be initialized yet, m_instance already has value, which will cause another thread calling GetInstance() method to get the m_instance pointer that has not yet been initialized;

Improvement 3 has another implementation in Linux. Linux provides a pthread_once() function that guarantees that a function is executed only once in a process.

Topics: Linux