设计模式——单例模式

本文详细介绍了单例模式,包括类的静态成员、单例模式的懒汉式与饿汉式实现,以及它们的线程安全问题。讨论了静态成员变量和静态成员函数的特性,并探讨了单例模式在不同场景下的选择和优缺点。

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

一、类的静态成员(static)

在介绍单例模式之前,首先需要了解静态成员变量静态成员函数。单例模式中保证一个类只能产生一个实例,确保该类的唯一性。主要就是依赖类的静态成员函数/静态成员变量的特殊性质。

1、静态成员变量(static)

(1)我们可以使用静态成员变量来实现多个对象实例共享数据的目标。静态成员变量属于类,但不属于某个具体的类,即使创建多个对象,也只为静态成员变量分配一份内存,所有对象使用的都是这份内存的数据。当某个对象修改了静态成员变量,也会影响到其它对象实例

(2)静态成员变量必须在类声明的外部进行初始化,声明时不需要关键字static。具体形式为:
<data_type> <class_type>::<value_name> = <true_value>。

(3)静态成员变量的内存不是在类声明时分配的,也不是在创建对象时分配的,而是在类外初始化时分配的,没有在类外初始化的静态成员变量是不能使用的。

(4)静态成员变量可以通过三种方式来访问:通过对象访问通过对象指针访问通过类作用域访问,三种方式等效。

(5)静态成员变量不占对象的内存,而是在所有对象之外开辟内存,即使不创建对象实例也可以访问。静态成员变量和普通的静态变量类似,都是在内存分区的全局数据区分配内存

2、静态成员函数(static)

(1)静态成员函数相当于带有命名空间的全局函数,静态函数不需要实例化就可以被调用,不会也不可以调用或操纵非静态成员

(2)在C++中,静态成员函数的主要目的是访问静态成员

(3)和静态成员变量类似,静态成员函数在声明时要加 static,在定义时不能加 static。静态成员函数可以通过类来调用,也可以通过对象来调用

(4)全局的静态函数普通函数的区别是:用static修饰的函数,限定在本源码文件中,不能被本源码文件以外的代码文件调用。而普通的函数,默认是extern的,也就是说它可以被其它代码文件调用。

二、单例模式

单例模式主要分为懒汉式饿汉式两种,两者之间的区别在于创建实例的时间不同

  • 懒汉式:在系统运行时,实例并不存在,只有当需要使用该实例时,才会去创建并使用实例。(需要考虑线程安全,避免两个线程同时需要使用该实例,同时创建)
  • 饿汉式:在系统一运行,就初始化创建实例,当需要使用实例时,直接调用即可。                (本身就是线程安全的

单例模式下的类的特点:

  • 构造函数析构函数都是privtae类型的,目的是禁止外部构造和析构;
  • 拷贝构造赋值构造函数private类型的,目的是禁止外部拷贝和赋值,确保实例的唯一性
  • 类里面有个获取实例的静态函数,可以全局访问,当其它对象需要访问该共享数据时,调用该静态函数获取。

1、懒汉式(线程不安全)

/*【单例模式指在整个系统生命周期里,保证一个类只能产生一个实例,确保该类的唯一性】*/
class SingleInstance {
public:
	//静态成员函数,获取单例对象
	static SingleInstance* GetInstance();
	//静态成员函数,释放单例对象
	static void deleteInstance();

	//打印对象实例地址
	void print();

private:
	//将构造函数和析构函数私有化,禁止外部构造和析构
	SingleInstance();
	~SingleInstance();

	//将其拷贝构造函数和赋值构造函数私有化,禁止外部拷贝和赋值
	SingleInstance(const SingleInstance& single_instance);
	const SingleInstance& operator=(const SingleInstance& single_instance);
private:
	//将实例对象声明为静态成员变量,只能在第一次进行初始化
	//之所以将对象设置为静态成员变量,是为了让多个线程能够共享该对象实例
	static SingleInstance* m_SingleInstance;
};
//静态成员变量必须在类外进行初始化,没有初始化的静态成员变量不能使用
//初始化方式:<data_type> <class>::<data_name> = <true_data> 
SingleInstance* SingleInstance::m_SingleInstance = nullptr;
/*
 * 通过静态成员函数获取静态成员变量——单例对象
 */
SingleInstance* SingleInstance::GetInstance()
{
	/*此处没有加锁,所以可能多个线程会同时为m_SingleInstance分配内存,造成m_SingleInstance地址不一致*/
	if (m_SingleInstance == nullptr)
	{
		m_SingleInstance = new SingleInstance;
	}
	return m_SingleInstance;
}
/* 
 * 通过静态成员函数释放静态成员变量——单例对象
 */  
void SingleInstance::deleteInstance()
{
	if (m_SingleInstance != nullptr)
	{
		delete m_SingleInstance;
		//释放后的指针必须赋空,避免出现野指针
		m_SingleInstance = nullptr;
	}
}
/*
 * 打印对象实例的地址
 */
void SingleInstance::print()
{
	cout << "我的实际地址是:" << this << endl;
}
/*
 * 构造函数
 */
SingleInstance::SingleInstance()
{
	cout << "我是构造函数" << endl;
}
/*
 * 析构函数
 */
SingleInstance::~SingleInstance()
{
	cout << "我是析构函数"<<endl;
}
/*-------------------------------------线程函数-------------------------------------*/
void PrintHello()
{
	//调用单实例对象的打印函数,打印实例地址
	SingleInstance::GetInstance()->print();
	auto id = std::this_thread::get_id();
	cout << "Hi,我是线程ID:[" << id << "]" << endl;
}
/*主函数*/
int main()
{
	cout << "开始创建线程--------------------------" << endl;
	for (int i = 0; i < 5; i++)
	{
		cout << "main() : 创建线程:[" << i << "]" << std::endl;
		thread th(PrintHello);
		th.join();
		
	}
	// 手动释放单实例的资源
	SingleInstance::deleteInstance();
	cout << "main() : 结束! " << std::endl;

	return 0;
}

由于在上述代码,在使用静态成员函数GetInstance()获取单例时,没有加锁操作,所以当多个线程同时第一次需要使用该对象实例,造成多个线程同时创建该实例,导致出现线程安全问题

2、懒汉式(加锁——线程安全)

/*【单例模式指在整个系统生命周期里,保证一个类只能产生一个实例,确保该类的唯一性】*/
class SingleInstance {
public:
	//静态成员函数,获取单例对象
	static SingleInstance* GetInstance();
	//静态成员函数,释放单例对象
	static void deleteInstance();

	//打印对象实例地址
	void print();

private:
	//将构造函数和析构函数私有化,禁止外部构造和析构
	SingleInstance();
	~SingleInstance();

	//将其拷贝构造函数和赋值构造函数私有化,禁止外部拷贝和赋值
	SingleInstance(const SingleInstance& single_instance);
	const SingleInstance& operator=(const SingleInstance& single_instance);
private:
	//将实例对象声明为静态成员变量,只能在第一次进行初始化
	//之所以将对象设置为静态成员变量,是为了让多个线程能够共享该对象实例
	static SingleInstance* m_SingleInstance;

	/*懒汉模式加锁,防止多个线程同时判断单例对象为nullptr*/
	static mutex singleMutex;
};

//静态成员变量必须在类外进行初始化,没有初始化的静态成员变量不能使用
//初始化方式:<data_type> <class>::<data_name> = <true_data> 
SingleInstance* SingleInstance::m_SingleInstance = nullptr;
//初始化懒汉式锁
mutex SingleInstance::singleMutex;
/*
 * 通过静态成员函数获取静态成员变量——单例对象【懒汉模式加锁】
 */
SingleInstance* SingleInstance::GetInstance()
{
	/*懒汉模式加锁*/
	if (m_SingleInstance == nullptr)
	{
		//【加锁——独占锁】,只有当前线程结束,其它线程才能对该锁singleMutex进行加锁
		unique_lock<mutex> lock(singleMutex);
		if (m_SingleInstance == nullptr)
		{
			m_SingleInstance = new SingleInstance;
		}
	} 
	return m_SingleInstance;
}

上述代码中在使用静态成员函数GetInstance()获取单例时,先判断该实例是否为空,如果为空需要首次创建,则对其进行lock加锁,待当前线程使用完该实例后,自动解锁。

3、饿汉式

/*【单例模式指在整个系统生命周期里,保证一个类只能产生一个实例,确保该类的唯一性】*/
class SingleInstance {
public:
	//静态成员函数,获取单例对象
	static SingleInstance* GetInstance();
	//静态成员函数,释放单例对象
	static void deleteInstance();

	//打印对象实例地址
	void print();

private:
	//将构造函数和析构函数私有化,禁止外部构造和析构
	SingleInstance();
	~SingleInstance();

	//将其拷贝构造函数和赋值构造函数私有化,禁止外部拷贝和赋值
	SingleInstance(const SingleInstance& single_instance);
	const SingleInstance& operator=(const SingleInstance& single_instance);
private:
	//将实例对象声明为静态成员变量,只能在第一次进行初始化
	//之所以将对象设置为静态成员变量,是为了让多个线程能够共享该对象实例
	static SingleInstance* m_SingleInstance;
};
//静态成员变量必须在类外进行初始化,没有初始化的静态成员变量不能使用
//初始化方式:<data_type> <class>::<data_name> = <true_data> 
//系统一运行,立马对该静态成员变量进行初始化
SingleInstance* SingleInstance::m_SingleInstance = new SingleInstance;
/*
 * 通过静态成员函数获取静态成员变量——单例对象
 */
SingleInstance* SingleInstance::GetInstance()
{
    //将静态成员变量直接返回
	return m_SingleInstance;
}

饿汉式在程序一开始就构造函数初始化了,所以本身就线程安全的。当后面开辟多个线程时,也是共享该实例。

4、局部静态变量(C++11 线程安全)

由于局部静态变量具有:多次调用,仅首次初始化有效;生存期为整个对象的使用期;位于全局变量区,多线程间共享。

C++11保证局部静态变量线程安全,C++98不能保证局部静态变量线程安全。

Single &Single::GetInstance()
{
    // 局部静态特性的方式实现单实例
    static Single signal;
    return signal;
}

三、特点与选择

  • 懒汉式是以时间换空间,适应于访问量较时;推荐使用内部静态变量的懒汉单例代码量少
  • 饿汉式是以空间换时间,适应于访问量较时,或者线程比较多的的情况
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值