[C++] Singleton mode [Two implementation methods]

 

Table of contents

 1. Understand the basic questions before understanding the singleton model

1. Design a class that cannot be copied

2. Design a class that can only create objects on the heap

3. Design a class that can only create objects on the stack

4. Design a class that cannot be inherited

2. Singleton mode

1. The concept of singleton pattern

2. Two implementation methods of singleton mode 

2.1 Lazy mode implements singleton mode

2.2 Hungry Pattern Implements Singleton Pattern


 1. Understand the basic questions before understanding the singleton model

1. Design a class that cannot be copied

Copying will only occur in two scenarios: copy constructor and assignment operator overloading, so wants to prohibit a class from copying, Just make that the class cannot call the copy constructor and assignment operator overloading .
C++98
Overload the copy constructor and assignment operatorOnly declare it undefined, and set its access permissions Just set it to private.
class CopyBan
{
    // ...
    
private:
    CopyBan(const CopyBan&);
    CopyBan& operator=(const CopyBan&);
    //...
};
reason:
1. is set to private: If it is only declared and not set to private , the user himself will If it is defined outside the class, it does not need to
Copying can be prohibited
2. Just declare that it is not defined: it is not defined because the function will not be called at all, and it is meaningless if it is defined, so it is not written
On the contrary, it is simpler, and if it is defined, it will not prevent internal copying of member functions.
C++11
 C++11 extension delete usage, delete In addition to releasing the resources applied by new , if is in the default member function followed
=delete means that the compiler deletes the default member function.
class CopyBan
{
    // ...
    CopyBan(const CopyBan&)=delete;
    CopyBan& operator=(const CopyBan&)=delete;
    //...
};

2. Design a class that can only create objects on the heap

Idea:

Creating an object must call the constructor (or copy construction: two disabling methods), so first disable the constructor (private construction function, there are two methods for copy construction), it is placed under private in the class, but how to create an object on the heap? Use a member function GetObj to create an object on the heap (because the constructor in the private member can be accessed within the class, but not outside the class), then why Do you want to modify GetObj with static? In this way, you can use Class name::GetObj to access it, instead of creating an object to access GetObj (because the object created before you call GetObj must be on the stack), in addition, the copy constructor must be set to private or directly =delete, that is, it cannot be used, because there is a scenario where you copy construct an object on the stack (that is, use copy constructor to create the object)

step:
1. Make the class's constructor private, The copy constructor is declared as delete to prevent others from calling copy to generate objects on the stack.
2. Provide a static member function, and complete the heap object in this static member function Creation
class HeapOnly
{
public:
	static HeapOnly* GetObj()
	{//专门用来在堆上创建对象
		return new HeapOnly;
    //注意这里是返回HeapOnly指针,那只是指针的拷贝,
    //而不是对象的拷贝,故不会调用构造函数
	}

	//C++11:防拷贝:拷贝构造函数声明成delete
	HeapOnly(const HeapOnly&) = delete;
private:
	//构造函数私有化
	HeapOnly()
	{}

	//C++98防拷贝:拷贝构造函数声明为私有
	//HeapOnly(const HeapOnly&);
};

int main()
{
	//HeapOnly hp; //在栈上创建,失败
	HeapOnly* p = HeapOnly::GetObj();//成功【创建一个在堆上的对象】

	std::shared_ptr<HeapOnly> sp1(HeapOnly::GetObj());
	std::shared_ptr<HeapOnly> sp2(HeapOnly::GetObj());

	//HeapOnly copy(*sp1);//用栈上的对象来拷贝构造copy是不行的,故要禁掉拷贝构造

	return 0;
}

3. Design a class that can only create objects on the stack

The only difference between and the idea that objects can only be created on the heap is: created stack objectsIt is necessary to pass a value and return, and the copy constructor must be called to copy the object, so the copy constructor cannot be disabled< /span>

class StackOnly
{
public:
	static StackOnly CreateObj()
	{
	//因为返回个匿名对象,传值返回,会调用拷贝构造
	//故不能禁掉拷贝构造
		return StackOnly();
	}

private:
	StackOnly()
	{}
};

int main()
{
	 StackOnly obj = StackOnly::CreateObj();
	 //StackOnly* ptr3 = new StackOnly; //失败
}

Below is the defective code:

The following code only disables the creation of data in the heap area, but the data created in the static area is stillCannot be blocked

//该方案存在一定程度缺陷,无法阻止在数据段(静态区)创建对象
class StackOnly
{
public:
	// 禁掉operator new可以把下面用new 调用拷贝构造申请对象给禁掉
	void* operator new(size_t size) = delete;
};

int main()
{
	StackOnly so;
	//new分为operator new + 构造函数
	//StackOnly* ptr3 = new StackOnly(obj); //失败
	static StackOnly sso;//在静态区上开辟成功

	return 0;
}

4. Design a class that cannot be inherited

C++98 way
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:
 static NonInherit GetInstance()
 {
 return NonInherit();
 }
private:
 NonInherit()
 {}
};
C++11 way
 final keyword, final modifies the class, indicating that the class cannot be inherited.
class A  final
{
    // ....
};

2. Singleton mode

1. The concept of singleton pattern

 Design Patterns:
Design Pattern ( Design Pattern ) is a set of that are used repeatedly and are known to most people. Classified summary of code design experience . Why is there such a thing as design pattern? Just like the development of human history will produce the art of war. In the beginning, when the tribes fought each other, they fought each other. Later, during the Spring and Autumn Period and the Warring States Period, the seven kingdoms often fought wars, and they discovered that there were routines for fighting. Later, Sun Tzu summarized "The Art of War". Sun Tzu's Art of War is similar.
The purpose of using design patterns: for code reusability, to make the code easier to understand by others, and to ensure code reliability. Design patterns make code writing truly engineering; design patterns are the cornerstone of software engineering, just like the structure of a building.
Singleton mode:
A class can only create one object, that is, singleton mode. This mode can ensure that there is only one instance of the class in the system and provide a global access point to access it. This instance is shared by all program modules. For example, in a server program, the server's configuration information is stored in a file. These configuration data are uniformly read by a singleton object, and then other objects in the service process obtain the configuration information through this singleton object. This method simplifies configuration management in complex environments.

There are two implementation modes of singleton mode: lazy mode and hungry mode.

2. Two implementation methods of singleton mode 

Design a class that can only create one object (singleton mode) 

Then the meaning of the question is to ensure that there is only one instance object globally

①. The general structure of the singleton pattern (defective)


class Singleton
{
public:
	static Singleton* GetInstance()
	{
		if (_pinst == nullptr)
		{//因为是静态成员变量,除了第一次为nullptr
	   	//再进来不是nullptr了,直接返回_pinst即可
			_pinst = new Singleton;
		}

		return _pinst;
	}

private:
	Singleton()
	{}

	Singleton(const Singleton& s) = delete;

	static Singleton* _pinst;//静态成员的声明
};

Singleton* Singleton::_pinst = nullptr;//静态成员的定义

int main()
{
	//Singleton s1; //失败
	//保证获取的对象每次都是同一个
	cout << Singleton::GetInstance() << endl;
	cout << Singleton::GetInstance() << endl;
	cout << Singleton::GetInstance() << endl;

	//Singleton copy(*Singleton::GetInstance());//无法调用拷贝构造

    return 0;
}

operation result:

The above codeThe flaw is a thread safety issue: Iftwo threads want a new object at the same time< /span>lock solution, what we want is only one object, Therefore, it leads to an error occurs (i.e. _pinst = new Singleton), then

 Let’s first look at the scenario where this error occurs:

In order to prevent the thread from running too fast and not achieving the effect we want to see the error situation, we use sleepsleep to assist a>

To solve the above problem, we uselock

2.1 Lazy mode implements singleton mode

①、Error code 1

//懒汉模式:第一次获取对象时,再创建对象
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		_mtx.lock();

		if (_pinst == nullptr)
		{//因为是静态成员变量,除了第一次为nullptr
		 //再进来不是nullptr了,直接返回_pinst即可
		    _pinst = new Singleton;
		}

		_mtx.unlock();

		return _pinst;
	}

	Singleton(const Singleton& s) = delete;

private:
	Singleton()
	{}

	static Singleton* _pinst;//静态成员的声明
	static mutex _mtx;
};

Singleton* Singleton::_pinst = nullptr;//静态成员的定义
mutex Singleton::_mtx;

The meaning of the above code is to create an object to ensure that only one thread is accessing it.It solves the problem that objects will not be created at the same time, but what if an exception is thrown if new fails? will also lock it for you, regardless of whether you Active unlocking will unlock after going out of scopeunique_lock: ? At this time, the thread being accessed is not unlocked, and other threads cannot access it, so you need to use

②. Use unique_lock to improve

③. Only need to lock for the first time

As long as _pinst points to the instance object that has been newed, there is no need to lock it.

④. Destruction of singleton mode objects

Generally, the globally unique object produced by new in singleton mode does not need to be released, because there is only one object in singleton mode in the entire program. It is always used and there is no need to release it.
If you just want to release, there are two ways:

①.Static function

②. Life cycle of static variables


#include<vector>
#include<thread>
#include<mutex>

namespace lazy_man
{
	//懒汉模式:第一次获取对象时,再创建对象
	class Singleton
	{
	public:
		static Singleton* GetInstance()
		{
			//_mtx.lock();

			unique_lock会锁,锁完之后不管你是否解锁,出了作用域他都会自动解锁
			而你现在就这一个地方需要锁,故再加个{}作用域
			//{
			//	unique_lock<mutex> lock(_mtx);
			//	if (_pinst == nullptr)
			//	{//因为是静态成员变量,除了第一次为nullptr
			//	 //再进来不是nullptr了,直接返回_pinst即可
			//		_pinst = new Singleton;
			//	}
			//}

			//双检查:
			if (_pinst == nullptr)
			{
				//加锁只是为了保护第一次
				{
					unique_lock<mutex> lock(_mtx);
					if (_pinst == nullptr)
					{//因为是静态成员变量,除了第一次为nullptr
					//再进来不是nullptr了,直接返回_pinst即可
						_pinst = new Singleton;
					//只要_pinst指向已经new出来的实例对象,就无须加锁了
					}
				}

			}

			//_mtx.unlock();

			return _pinst;
		}

		//如果你就想释放这个对象的话,自己写个静态函数即可,手动调
		static void DelInstance()
		{
			unique_lock<mutex> lock(_mtx);
			delete _pinst;
			_pinst = nullptr;
		}

		Singleton(const Singleton& s) = delete;

	private:
		Singleton()
		{}


		static Singleton* _pinst;//静态成员的声明
		static mutex _mtx;
	};

	Singleton* Singleton::_pinst = nullptr;//静态成员的定义
	mutex Singleton::_mtx;

	//1、如果要手动释放单例对象,可以调用DelInstance
	//2、如果需要程序结束时,正常释放单例对象,可以加入下面的设计
	class GC
	{
	public:
		~GC()
		{
			Singleton::DelInstance();
		}

	};

	static GC gc;//main函数结束就会调用它的析构函数,进而释放_pinst

	void x()
	{
		Singleton s1; //失败
		保证获取的对象每次都是同一个
		//cout << Singleton::GetInstance() << endl;
		//cout << Singleton::GetInstance() << endl;
		//cout << Singleton::GetInstance() << endl;

		Singleton copy(*Singleton::GetInstance());//无法调用拷贝构造

		//代码中存在线程问题:若多个线程同时获取一个对象呢?
		vector<std::thread> vthreads;
		int n = 4;
		for (size_t i = 0; i < n; ++i)
		{
			vthreads.push_back(std::thread([]()
				{
					//cout << std::this_thread::get_id() << ":";
					cout << Singleton::GetInstance() << endl;
				}));//线程对象里面用了一个lambda表达式
		}

		for (auto& t : vthreads)
		{
			t.join();
		}
	}
}

int main()
{
	lazy_man::x();

	return 0;
}

operation result:

2.2 Hungry Pattern Implements Singleton Pattern

Hungry Man Mode has static member variables. Static variables are created before the program runs and always exist during the entire running of the program. They Always retains its original value,unless it is assigned a different value or the program terminates. JustBecause the program is created before, there is only the main thread at this time, and there is no thread safety issue.

namespace hungry_man
{
	//饿汉模式 --main函数之前就创建对象
	class Singleton
	{
	public:
		static Singleton* GetInstance()
		{
			return &_inst;
		}

		Singleton(const Singleton& s) = delete;

	private: 
		Singleton()
		{}

		static Singleton _inst;
	};

	//static对象是在main函数之前创建的,这时只有主线程,故不存在线程安全问题
	Singleton Singleton::_inst; 

	void x()
	{
		Singleton s1; //失败
		保证获取的对象每次都是同一个
		//cout << Singleton::GetInstance() << endl;
		//cout << Singleton::GetInstance() << endl;
		//cout << Singleton::GetInstance() << endl;

		Singleton copy(*Singleton::GetInstance());//无法调用拷贝构造

		//代码中存在线程问题:若多个线程同时获取一个对象呢?
		vector<std::thread> vthreads;
		int n = 4;
		for (size_t i = 0; i < n; ++i)
		{
			vthreads.push_back(std::thread([]()
				{
					//cout << std::this_thread::get_id() << ":";
					cout << Singleton::GetInstance() << endl;
				}));//线程对象里面用了一个lambda表达式
		}

		for (auto& t : vthreads)
		{
			t.join();
		}
	}
}

int main()
{
	hungry_man::x();

	return 0;
}


2.3 The difference between lazy man and hungry man models


Guess you like

Origin blog.csdn.net/m0_74044018/article/details/134253606