【zz】C++设计模式——单例模式

【zz】C++设计模式——单例模式

转载 Word哥 2020-04-07 19:19:08


1.单例模式

2. 懒汉式

3. 饿汉模式(线程安全)

4. 结论

1.单例模式

定义:保证一个类只有一个实例,并提供一个访问它的全局访问点,使得系统中只有唯一的一个对象实例。

应用场景:

          1. 需要频繁实例化然后销毁的对象

2. 创建对象耗时过多或者耗资源过多,但又经常用到的对象

3. 有状态的工具类对象

4. 频繁访问数据库或文件的对象

5. 以及其他要求只有一个对象的场景

常用于管理资源,如日志、线程池

优点:

1. 在内存中只有一个对象,节省内存空间

2. 避免频繁的创建销毁对象,可以提高性能

3. 避免对共享资源的多重占用

4. 可以全局访问

实现要点:

   1. 单例模式的类只提供私有的构造函数

   2. 类定义中含有一个该类的静态私有对象

   3. 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象

单例模式又分为两种基本的情形:饿汉式和懒汉式

2. 懒汉式(3种)

定义:用的时候才加载,在类的public,getInstance方法中创建对象,场景可能有不用的情况,需要通过手动加锁实现线程安全。

特点: 延迟加载,用时间换取空间

在C++ 中一般都使用懒汉式单例,多线程下的懒汉式单例模式可能会有线程安全,异常安全问题。

假设有两个线程同时运行了这个单例类,同时运行到了判断 if 语句,并且当时,instance 实例确实没有被初始化呢,那么两个线程都会去运行并创建实例,此时就不满足单例类的要求了。那么我们需要写上线程同步的功能。

`懒汉模式——样例1代码如下(非线程安全):

Singleton.h

#pragma once
#include 
using namespace std;
 
//懒汉模式
class CSingleton
{
     
public:
	static CSingleton* GetInstance()
	{
     
		if (m_pSingleton == NULL)//判断对象是否已经存在
		{
     
			m_pSingleton = new CSingleton();
		}
		return m_pSingleton;
	}
public:
	//定义一个嵌套类专门用来释放单例对象,利用静态变量在程序结束会自动释放的特点
	class Destroy
	{
     
	public:
		~Destroy()
		{
     
			if (CSingleton::m_pSingleton)
			{
     
				delete CSingleton::m_pSingleton;
				cout << "~CSingleton()" << endl;
			}
		}
	};
private:
	//构造函数私有化 防止外部私自创建对象
	CSingleton()
	{
     
		cout << "Singleton()" << endl;
	}
private:
	static Destroy destroy;
	static CSingleton *	m_pSingleton;
};
Singleton.cpp

#include "stdafx.h"
#include "Singleton.h"
 
//懒汉模式定义类时不创建唯一的类对象,而是当使用的时候才唯一创建该单例的唯一对象,所以初始化为NULL;
CSingleton* CSingleton::m_pSingleton = NULL;
CSingleton::Destroy destroy;
main.cpp

#include "stdafx.h"
#include "Singleton.h"
int main()
{
     
	cout << "GetInstance start!" << endl;
	CSingleton* s1 = CSingleton::GetInstance();
	CSingleton* s2 = CSingleton::GetInstance();
	cout << s1 << endl;
	cout << s2 << endl;
    return 0;
}

运行结果:

  • 结论:程序运行后实例化s1的时候才进行的单例对象的构造。

  • 缺点: 多线程情况下,如果两个线程同时首次调用GetInstance方法且同时检测到singleton是NULL,则两个线程会同时构造一个实例给singleton,会发生错误,违背了单例模式思想。

懒汉模式——样例2代码如下(双重检查锁):

Singleton.h

#pragma once
#include 
using namespace std;
 
//懒汉模式
class CSingleton
{
     
public:
	static CSingleton* GetInstance()
	{
     
		if (m_pSingleton == NULL)//判断对象是否已经存在
		{
     
			//先判断单例对象是否已经产生,若已经产生就不用进行加锁解锁操作直接返回对象的指针
			cout << "add lock..." << endl;
			
			std::lock_guard<std::mutex> lock(m_mutex);
			m_pSingleton = new CSingleton();
 
			cout << "unlock..." << endl;
		}
		return m_pSingleton;
	}
public:
	//定义一个嵌套类专门用来释放单例对象,利用静态变量在程序结束会自动释放的特点
	class Destroy
	{
     
	public:
		~Destroy()
		{
     
			if (CSingleton::m_pSingleton)
			{
     
				delete CSingleton::m_pSingleton;
				cout << "~CSingleton()" << endl;
			}
		}
	};
private:
	//构造函数私有化 防止外部私自创建对象
	CSingleton()
	{
     
		cout << "Singleton()" << endl;
	}
private:
	static Destroy		destroy;
	static CSingleton*	m_pSingleton;
	static std::mutex	m_mutex;		//原子锁
};
Singleton.cpp

#include "stdafx.h"
#include "Singleton.h"
 
//懒汉模式定义类时不创建唯一的类对象,而是当使用的时候才唯一创建该单例的唯一对象,所以初始化为NULL;
CSingleton* CSingleton::m_pSingleton = NULL;
CSingleton::Destroy destroy;
std::mutex CSingleton::m_mutex;
main.cpp

#include "stdafx.h"
#include "Singleton.h"
int main()
{
     
	cout << "GetInstance start!" << endl;
	CSingleton* s1 = CSingleton::GetInstance();
	CSingleton* s2 = CSingleton::GetInstance();
	cout << s1 << endl;
	cout << s2 << endl;
    return 0;
}

运行结果:

懒汉模式——样例3代码如下(局部静态变量) :推荐

Singleton.h

#pragma once
#include 
using namespace std;
 
//懒汉模式--局部静态变量
class CSingleton
{
     
public:
	static CSingleton* GetInstance()
	{
     
		//std::lock_guard lock(m_mutex);
		static CSingleton m_instance;  //局部静态变量
		return &m_instance;
	}
private:
	//构造函数私有化 防止外部私自创建对象
	CSingleton()
	{
     
		cout << "Singleton()" << endl;
	}
	//static std::mutex	m_mutex;		//原子锁
};
 
//std::mutex CSingleton::m_mutex;
 

main.cpp

#include "stdafx.h"
#include "Singleton.h"
int main()
{
     
    cout << "GetInstance start!" << endl;
    CSingleton::GetInstance();
    return 0;
}

运行结果:

  • 结论:局部静态变量不仅只会初始化一次,而且还是线程安全的(C++11之前不能这么写)。

3. 饿汉模式(线程安全)

定义:定义类的时候就已经实例化产生了该单例模式的唯一的对象,而后面使用的时候只是调用该单例对象。

特点:开始就加载了,空间换时间,因为一开始就创建了实例,每次使用直接返回。

优点:不需要加锁,执行效率高,线程安全。

缺点:不管是不是用都会初始化,浪费内存。

Singleton.h

#pragma once
#include 
using namespace std;
 
//饿汉模式
class CSingleton
{
     
 
public:
	static CSingleton* GetInstance()
	{
     
		return m_pSingleton;
	}
 
	class Destroy
	{
     
	public:
		~Destroy()
		{
     
			if (CSingleton::m_pSingleton)
			{
     
				delete CSingleton::m_pSingleton;
				cout << "~CSingleton()" << endl;
			}
		}
	};
private:
	CSingleton()
	{
     
		cout << "Singleton()" << endl;
	}
private:
	static CSingleton *m_pSingleton;
	static Destroy destroy;
};
Singleton.cpp

#include "stdafx.h"
#include "Singleton.h"
 
//饿汉模式定义类时直接创建唯一的单例对象
CSingleton* CSingleton::m_pSingleton = new CSingleton();
CSingleton::Destroy destroy;
main.cpp

#include "stdafx.h"
#include "Singleton.h"
int main()
{
     
	cout << "GetInstance start!" << endl;
	CSingleton* s1 = CSingleton::GetInstance();
	CSingleton* s2 = CSingleton::GetInstance();
	cout << s1 << endl;
	cout << s2 << endl;
    return 0;
}
  • 结论:在程序没有开始运行时就已产生了唯一的实例化对象,而实例化s1的时候唯一的单例对象已经存在,这时候只是在调用该已经存在的单例对象。
  1. 结论
    懒汉模式中局部静态变量版本在C++11后是线程安全的,使用之前版本需要加锁。
    饿汉模式线程安全,但存在潜在问题(不管是不是用都会初始化,浪费内存)。
    注意:上面讨论的线程安全指的是GetInstance()是线程安全的,假如多个线程都获取类的对象,如果只是只读操作,完全OK,但是如果有线程要修改,有线程要读取,那么类A自身的函数需要自己加锁防护,不是说线程安全的单例也能保证修改和读取该对象自身的资源也是线程安全的。

你可能感兴趣的:(c/c++)