C++ 设计模式->单例模式


单例模式

特点: 该类有且只有一个实例,并只能通过接口函数获取到该类的实例

所以,该类的设计需要满足:
1.构造函数私有化
2.禁止赋值和拷贝
3.提供获取该类的实例接口

代码演示

1.简单的单例实现

#include 
using namespace std;

class MyClass
{
private:
	MyClass(){  };    // 私有化构造函数
	MyClass(MyClass&) = delete;				     // 禁止拷贝构造函数
	MyClass& operator=(const MyClass&) = delete; // 禁止赋值运算符

public:
	~MyClass() { };

public:
	static MyClass* instance() {  // 单例接口函数
		if (nullptr == _instance) {
			_instance = new MyClass();
		}
		return _instance;
	}

private:
	static MyClass *_instance;
};

MyClass *MyClass::_instance = nullptr;

int main(int arg, char **argv)
{	
	MyClass* a = MyClass::instance();  // 获取实例
	MyClass* b = MyClass::instance();  // 获取实例

	cout << "a=" << a << endl;
	cout << "b=" << b << endl;

	return 0;
}

调试结果:可看到对象 a 和对象 b 是一样的;

那多线程的情况下上边的代码还能适用吗

我们测试一下

修改一下代码

#include 
#include 
using namespace std;

class MyClass
{
private:
	MyClass()  // 私有化构造函数
	{ 
		// 假设初始化时需要的时间
		this_thread::sleep_for(chrono::milliseconds(100)); 
	};   
	MyClass(MyClass&) = delete;				     // 禁止拷贝构造函数
	MyClass& operator=(const MyClass&) = delete; // 禁止赋值运算符

public:
	~MyClass() { };

public:
	static MyClass* instance() { // 单例接口函数
		if (nullptr == _instance) {
			_instance = new MyClass();
		}
		return _instance;
	}

private:
	static MyClass *_instance;
};

MyClass *MyClass::_instance = nullptr;

MyClass *a = nullptr;
MyClass *b = nullptr;

// 线程函数1
void threadFun1() 
{
	a = MyClass::instance();  // 获取实例
}
// 线程函数2
void threadFun2()
{
	b = MyClass::instance();  // 获取实例
}

int main(int arg, char **argv)
{	
	// 开启2个线程,差不多同时去访问接口
	thread t1(threadFun1);
	thread t2(threadFun2);
	t1.detach();
	t2.detach();

	// 测试使用延迟一会儿。
	this_thread::sleep_for(chrono::milliseconds(1000));
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;

	return 0;
}

调试结果

可以看到 a 对象 和 b 对象是不一样的,与我们想要的单例不符合

那这时候如何去改进呢。

没错,给它加上锁

接下来,我们给接口函数加上锁

#include 
#include 
#include 
using namespace std;

static mutex _mutex;
class MyClass
{
private:
	MyClass()  // 私有化构造函数
	{ 
		// 假设初始化时需要的时间
		this_thread::sleep_for(chrono::milliseconds(100)); 
	};   
	MyClass(MyClass&) = delete;				     // 禁止拷贝构造函数
	MyClass& operator=(const MyClass&) = delete; // 禁止赋值运算符

public:
	~MyClass() { };

public:
	static MyClass* instance() { // 单例接口函数
		if (nullptr == _instance) {
			_mutex.lock();  // 加锁
			if (nullptr == _instance) {
				_instance = new MyClass();
			}
			_mutex.unlock(); // 解锁
		}
		return _instance;
	}

private:
	static MyClass *_instance;
};

MyClass *MyClass::_instance = nullptr;

MyClass *a = nullptr;
MyClass *b = nullptr;

// 线程函数1
void threadFun1() 
{
	a = MyClass::instance();  // 获取实例
}
// 线程函数2
void threadFun2()
{
	b = MyClass::instance();  // 获取实例
}

int main(int arg, char **argv)
{	
	// 开启2个线程,差不多同时去访问接口
	thread t1(threadFun1);
	thread t2(threadFun2);
	t1.detach();
	t2.detach();

	// 测试使用延迟一会儿。
	this_thread::sleep_for(chrono::milliseconds(1000));
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;

	return 0;
}

调试结果

可以看到 a 和 b 是一样的了,符合单例。

这样就好了吗?

但还有一个问题,我再修改一下代码

在构造函数和析构函数添加一句输出语句

#include 
#include 
#include 
using namespace std;

static mutex _mutex;
class MyClass
{
private:
	MyClass()  // 私有化构造函数
	{ 
		// 假设初始化时需要的时间
		this_thread::sleep_for(chrono::milliseconds(100)); 
		cout << "我是构造函数" << endl;
	};   
	MyClass(MyClass&) = delete;				     // 禁止拷贝构造函数
	MyClass& operator=(const MyClass&) = delete; // 禁止赋值运算符

public:
	~MyClass() { cout << "我是析构函数" << endl; };

public:
	static MyClass* instance() { // 单例接口函数
		if (nullptr == _instance) {
			_mutex.lock();  // 加锁
			if (nullptr == _instance) {
				_instance = new MyClass();
			}
			_mutex.unlock(); // 解锁
		}
		return _instance;
	}

private:
	static MyClass *_instance;
};

MyClass *MyClass::_instance = nullptr;

MyClass *a = nullptr;
MyClass *b = nullptr;

// 线程函数1
void threadFun1() 
{
	a = MyClass::instance();  // 获取实例
}
// 线程函数2
void threadFun2()
{
	b = MyClass::instance();  // 获取实例
}

int main(int arg, char **argv)
{	
	// 开启2个线程,差不多同时去访问接口
	thread t1(threadFun1);
	thread t2(threadFun2);
	t1.detach();
	t2.detach();

	// 测试使用延迟一会儿。
	this_thread::sleep_for(chrono::milliseconds(1000));
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;

	return 0;
}

调试结果

我们发现了什么?

该类的实例没有被析构

也就是需要手动去析构

但有时候我们会忘了去给对象析构,这样就造成内存泄漏

那这时候怎么办呢? 使用智能指针!

嗯,智能指针是可以完成这个重任,但本次不采用智能指针的形式

我们修改一下代码

#include 
#include 
#include 
using namespace std;

static mutex _mutex;
class MyClass
{
private:
	MyClass()  // 私有化构造函数
	{ 
		// 假设初始化时需要的时间
		this_thread::sleep_for(chrono::milliseconds(100)); 
		cout << "我是构造函数" << endl;
	};   
	MyClass(MyClass&) = delete;				     // 禁止拷贝构造函数
	MyClass& operator=(const MyClass&) = delete; // 禁止赋值运算符

public:
	~MyClass() { cout << "我是析构函数" << endl; };

public:
	static MyClass& instance() { // 单例接口函数
	    static MyClass _instance;
		return _instance;
	}

};


MyClass *a = nullptr;
MyClass *b = nullptr;

// 线程函数1
void threadFun1() 
{
	a = &MyClass::instance();  // 获取实例
}
// 线程函数2
void threadFun2()
{
	b = &MyClass::instance();  // 获取实例
}

int main(int arg, char **argv)
{	
	// 开启2个线程,差不多同时去访问接口
	thread t1(threadFun1);
	thread t2(threadFun2);
	t1.detach();
	t2.detach();

	// 测试使用延迟一会儿。
	this_thread::sleep_for(chrono::milliseconds(1000));
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;

	return 0;
}

调试结果

把它修改为 static 方式后,不用线程锁也不用手动去释放了

不好的地方可能就是需要用引用的方式获取实例

目前来说,本人觉得该方式应该是比较好的一种方式了

至于什么懒汉式,饿汉之类的区分。。。

这里就不区分了。。。。

理解就好了。

...

_End

完事儿。