设计模式之单例模式(C++)

本文深入探讨了设计模式中的单例模式,解释了如何确保一个类只有一个实例并提供全局访问点。通过示例代码展示了线程不安全和线程安全的单例模式实现,以及在多线程环境下可能遇到的问题和解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

设计模式之单例模式

保证一个类仅有一个实例,并提供一个访问他的全局访问点。通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且可以提供一个访问该实例的接口。其UML图如下:
单例模式UML图
其示例代码如下:

// SingeltonModel.h文件
#pragma once
// 线程不安全类
class Singelton
{
private:
	Singelton() {}
	static Singelton * m_singel;
public:
	static Singelton * getInstance()
	{
		if (nullptr == m_singel)
		{
			std::cout << "创建实例" << std::endl;
			m_singel = new Singelton();
		}	
		return m_singel;
	}
};
Singelton * Singelton::m_singel = nullptr;

测试代码如下:

#include <iostream>
#include "SingeltonModel.h"

int main()
{
	using namespace std;
	// 单例模式
	Singelton * p1 = Singelton::getInstance();
	Singelton * p2 = Singelton::getInstance();
	if (p1 == p2)
		std::cout << "一致" << std::endl;
	else
		std::cout << "不一致" << std::endl;

	getchar();
	return 0;

测试结果如下图:
在这里插入图片描述
以上代码是有安全隐患的,如果该类在多线程中使用的话。当多个线程都第一次访问该类的是实例的时候有可能会出现创建两个实例,这样就出现内存泄漏了。并且两次的到的实例不是同一个,如下代码:

#include <iostream>
#include <thread>
#include <Windows.h>
#include "SingeltonModel.h"

void func1()
{
	Sleep(1);
	Singelton * p1 = Singelton::getInstance();
	std::cout << "func1 thread:" << p1 << std::endl;
}

void func2()
{
	Sleep(1);
	Singelton * p2 = Singelton::getInstance();
	std::cout << "func2 thread:" << p2 << std::endl;
}

int main()
{
	using namespace std;
	// 单例模式
	std::thread thread1(func1), thread2(func2);
	thread1.detach();
	thread2.detach();
	std::cout << "主线程" << std::endl;

	getchar();
	return 0;
}

输出结果如下图:
在这里插入图片描述
由上图可看出,创建了两次实例,两个线程得到的实例并不是同一个。这是因为两个线程同时运行,当调用getInstance()时,实例并没有创建,于是两个线程都进入了创建实例的代码块,于是就创建了两个实例。而第一个被创建的实例被顶替后也没有被释放,这就是内存泄漏,还有先调用到创建实例的线程还得到了错误的实例,这样会造成逻辑错误的。

解决办法有两种,一种办法是在所有要访问该类实例的代码执行前该类先创建实例,另一种是在该类的访问实例的代码中加入线程同步的内容。实例代码如下:

方法一:

#include <iostream>
#include <thread>
#include <Windows.h>
#include "SingeltonModel.h"

void func1()
{
	Sleep(1);
	Singelton * p1 = Singelton::getInstance();
	std::cout << "func1 thread:" << p1 << std::endl;
}

void func2()
{
	Sleep(1);
	Singelton * p2 = Singelton::getInstance();
	std::cout << "func2 thread:" << p2 << std::endl;
}

int main()
{
	using namespace std;
	// 单例模式
	// 在开启线程前,先获取一次实例。
	Singelton::getInstance();
	std::thread thread1(func1), thread2(func2);
	thread1.detach();
	thread2.detach();
	std::cout << "主线程" << std::endl;

	getchar();
	return 0;
}

输出结果如下图:
在这里插入图片描述
方法二:

// SingeltonModel.h文件
#include <mutex>
#pragma once

// 线程安全类
class SingeltonThread
{
private:
	SingeltonThread() {}
	static SingeltonThread * m_instance;
	static std::mutex m_mutex;
public:
	static SingeltonThread * getInstance()
	{
		if (nullptr == m_instance)
		{
			m_mutex.lock();
			if (nullptr == m_instance)
				m_instance = new SingeltonThread();
			m_mutex.unlock();
		}
		return m_instance;
	}
};
SingeltonThread * SingeltonThread::m_instance = nullptr;
std::mutex SingeltonThread::m_mutex;

测试代码如下:

#include <iostream>
#include <thread>
#include <Windows.h>
#include "SingeltonModel.h"

void func1()
{
	Sleep(1);
	SingeltonThread * p1 = SingeltonThread::getInstance();
	std::cout << "func1 thread:" << p1 << std::endl;
}

void func2()
{
	Sleep(1);
	SingeltonThread * p2 = SingeltonThread::getInstance();
	std::cout << "func2 thread:" << p2 << std::endl;
}

int main()
{
	using namespace std;
	// 单例模式
	// 线程安全
	std::thread thread1(func1), thread2(func2);
	thread1.detach();
	thread2.detach();
	std::cout << "主线程" << std::endl;
	
	getchar()
	return 0;

输出结果如下图:
在这里插入图片描述
这两种方法都各有优势,方法一实现简单,但在复杂的系统中不太安全。方法二实现稍微复杂,而且每次访问实例都要lock(mutex),消耗更大。但是方法二在任何情况下都很安全。总之方法一适合在系统不是太复杂的情况下使用,方法二在系统比较复杂的情况下使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值