C++单例设计模式

发布时间:2023年12月29日

C++单例设计模式

单例设计模式介绍

单例模式指的是,无论怎么获取,永远只能得到该类类型的唯一一个实例对象,那么设计一个单例就必须要满足下面三个条件:

  1. 构造函数私有化,这样用户就不能任意定义该类型的对象了
  2. 定义该类型唯一的对象
  3. 通过一个static静态成员方法返回唯一的对象实例

在写单例设计模式的时候,我们就可以采用这样的步骤来写单例模式

饿汉式单例设计模式

#include <iostream>

//饿汉式单例设计模式
class Single
{
public:
	//3. 获取类的唯一实例对象的接口方法
	static Single* getInstacne() { return &instance; }
	void printSingle()
	{
		std::cout << "hello codechen" << char(10);
	}
private:
	//2. 定义一个唯一的类的实例化的对象
	static Single instance;

	//1. 构造函数的私有化,不能够自己主动的去调用构造函数
	Single()
	{//在实际的项目中,构造函数可能会需要做很多的工作
	//比如说对一些成员变量的初始化,对一些数据的读取等等

	}

	Single(const Single&) = delete;
	Single& operator = (const Single&) = delete;
};
Single Single::instance{};

int main()
{
	Single* ptr1 = Single::getInstacne();
	ptr1->printSingle();
	delete ptr1;
	return 0;
}

懒汉式单例设计模式

#include <iostream>

//懒汉式单例设计模式
class Single
{
public:
	//3. 获取类的唯一实例对象的接口方法
	static Single* getInstacne() 
	{ 
		std::cout << "hello codechen,你成功的调用了一次构造函数" << char(10);
		if (instance == nullptr)
		{
			std::cout << "在这里对instance进行了初始化" << char(10);
			instance = new Single();
		}
		return instance;
	}
private:
	//2. 定义一个唯一的类的实例化的对象,为指针
	static Single* instance;

	//1. 构造函数的私有化,不能够自己主动的去调用构造函数
	Single()
	{//在实际的项目中,构造函数可能会需要做很多的工作
	//比如说对一些成员变量的初始化,对一些数据的读取等等

	}

	Single(const Single&) = delete;
	Single& operator = (const Single&) = delete;
};
Single* Single::instance = nullptr;

int main()
{
	Single* ptr1 = Single::getInstacne();
	Single* ptr2 = Single::getInstacne();
	Single* ptr3 = Single::getInstacne();
    
    delete ptr1;
    delete ptr2;
    delete ptr3;
    
	return 0;
}

什么是可重入函数

下面讲到线程安全问题,先引入一个可重入函数的概念

可重入函数是指在程序中可以被多个任务安全地调用的函数。这类函数在执行过程中不会修改全局变量,而是使用局部变量和线程特定数据等机制来保存数据。可重入函数在收到信号后不会导致数据出错,因此可以在信号处理函数中使用。与之相反,不可重入函数在收到信号后可能会修改全局变量,从而导致程序出现不可预料的后果。

在Linux系统编程中,为了提高程序的稳定性,应尽量使用可重入函数进行信号处理。同时,避免在信号处理函数中使用不可重入函数,以降低信号处理函数的复杂性。

可重入函数的特点:

  1. 使用局部变量而非全局变量。
  2. 避免使用静态数据结构。
  3. 不调用malloc()或free()等内存分配函数。
  4. 使用线程特定数据(如pthread_key_t)保存数据,以实现多线程间的数据隔离。

总之,可重入函数是在多任务、多线程环境下保证程序稳定性的重要手段。

为什么懒汉模式可能会涉及到线程安全的问题

	static Single* getInstacne() 
	{ 
		std::cout << "hello codechen,你成功的调用了一次构造函数" << char(10);
		if (instance == nullptr)
		{
			//在这里我们进行多种的操作,比如说开闭内存,构造对象和给Single赋值等
			std::cout << "在这里对instance进行了初始化" << char(10);
			instance = new Single();
		}
		return instance;
	}

我们看到这个函数,在if的判断语句中,在这里我们进行多种的操作,比如说开闭内存,构造对象和给Single赋值等。

如果一个线程,比如线程A进来了,看到这个instance == nullptr,会对她进行操作,这个时候如果线程B也进来了,这个线程A还没有对instance 赋值的话,这个线程B也是认为这个instance是没有赋值的,所以她也是会进入到去执行if内的语句。

如何解决这个问题

在这里我们最好的解决方式就是锁加上双重判断的方式

我们为什么要双重判断?

比如我线程A进来了,A拿到了锁,但是这个时候没有对instance赋值,线程B也是可以进来的,但是拿不到锁会阻塞在那个地方,如果线程A结束了,线程B还是会去执行if里面的语句,所以这里还要再加上一个if的双重判断。

#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
std::mutex mylock;
//线程安全的,懒汉式单例设计模式
class Single
{
public:
	//3. 获取类的唯一实例对象的接口方法
	//锁加上双重判断
	static Single* getInstacne() 
	{ 
		std::cout << "hello codechen,你成功的调用了一次构造函数" << char(10);
		if (instance == nullptr)
		{
			lock_guard<std::mutex> guard(mylock);
			if (instance == nullptr)
			{
				//在这里我们进行多种的操作,比如说开闭内存,构造对象和给Single赋值等
				std::cout << "在这里对instance进行了初始化" << char(10);
				instance = new Single();
			}
		}
		return instance;
	}
private:
	//2. 定义一个唯一的类的实例化的对象,为指针
	static Single* volatile instance;

	//1. 构造函数的私有化,不能够自己主动的去调用构造函数
	Single()
	{//在实际的项目中,构造函数可能会需要做很多的工作
	//比如说对一些成员变量的初始化,对一些数据的读取等等

	}

	Single(const Single&) = delete;
	Single& operator = (const Single&) = delete;
};
Single* Single::instance = nullptr;

int main()
{
	Single* ptr1 = Single::getInstacne();
	Single* ptr2 = Single::getInstacne();
	Single* ptr3 = Single::getInstacne();
    
    delete ptr1;
    delete ptr2;
    delete ptr3;
	return 0;
}

关于volatile的作用

在这里插入图片描述

附加问题:

#include <iostream>
using namespace std;

class CSingleton
{
public:
	static CSingleton* getInstance()
	{
		static CSingleton single; // 懒汉式单例模式,定义唯一的对象实例
		return &single;
	}
private:
	static CSingleton *single;
	CSingleton() { cout << "CSingleton()" << endl; }
	~CSingleton() { cout << "~CSingleton()" << endl;}
	CSingleton(const CSingleton&);
};
int main()
{
	CSingleton *p1 = CSingleton::getInstance();
	CSingleton *p2 = CSingleton::getInstance();
	CSingleton *p3 = CSingleton::getInstance();
    
    delete ptr1;
    delete ptr2;
    delete ptr3;
	return 0;
}

在这里插入图片描述

对于static静态局部变量的初始化,编译器会自动对它的初始化进行加锁和解锁控制,使静态局部变量的初始化成为线程安全的操作,不用担心多个线程都会初始化静态局部变量,因此上面的懒汉单例模式是线程安全的单例模式!

文章来源:https://blog.csdn.net/m0_73202575/article/details/135294368
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。