单例模式详细讲解

一.定义

单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点

特点:

1.构造函数和析构函数私有化

2.禁用拷贝构造函数和赋值运算符重载(=delete)

3.利用静态成员函数和静态成员变量来给外界提供访问

二.恶汉式

恶汉是非常霸道的,由此可见,对于恶汉式,我们程序加载时立即创建单例实例(无论是否需要)

代码如下:

cpp 复制代码
//恶汉式:
class Singleton 
{
public:
	//利用静态成员函数和静态成员变量来给外界提供访问
	static Singleton GetInstance()
	{
		return _instance;
	}	
	//禁用拷贝构造和赋值运算符
	Singleton(const Singleton&) =delete;
	Singleton& operator=(const Singleton&)=delete;
priavte:
	//构造析构私有化:
	Singleton()
	{
	}
	~Singleton()
	{
	}
	static Singleton _instance;//定义一个对象 
};
//static类外实例化:
Singleton Singleton::_instance;

优点:

  • 线程安全(C++11保证静态变量的线程安全初始化)
  • 程序启动时就创建实例
  • 简单直接

缺点:

  • 本质是通过空间换来的,可能导致空间浪费

三.懒汉式

懒汉本质在于懒,说明只有当我们需要时才会创建单例对象,具有延迟实例化特点,通过调用GetInstance()函数来创建对象

优点;

按需创建对象,避免浪费空间

缺点:

基础实现是非线程安全的,需额外处理多线程问题

下面我们一一来讲解不同版本:

cpp 复制代码
//懒汉式:
//初始版本--1
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		if(nullptr==_instance)
		{
			//创建对象
			_instance =new Singleton;
		}
		return _instance;
	}
	//禁用拷贝构造和赋值运算符
	Singleton(const Singleton&) =delete;
	Singleton& operator=(const Singleton&)=delete;
priavte:
	//构造析构私有化:
	Singleton()
	{
	}
	~Singleton()
	{
	}
	static Singleton* _instance;//定义一个对象指针 
};
//类对象实例化: 
Singleton* Singleton::_instance=nullptr;

该版本问题如下:

该代码不是线程、进程安全的,具体是指如果多个线程同时调用到GetInstance中的if语句且都进入,就会new两个以上对象,无法确保只有一个类对象

解决方法:加锁

cpp 复制代码
//懒汉式:
//初始版本--2
#include <mutex>
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		//单检测法: 
		_mutex.lock();
		if(nullptr==_instance)
		{
			//创建对象
			_instance =new Singleton;
		}
		_mutex.unlock();
		return _instance;
	}
	//禁用拷贝构造和赋值运算符
	Singleton(const Singleton&) =delete;
	Singleton& operator=(const Singleton&)=delete;
priavte:
	//构造析构私有化:
	Singleton()
	{
	}
	~Singleton()
	{
	}
	static Singleton* _instance;//定义一个对象指针
	static std::mutex _mutex; 
};
//类对象实例化: 
Singleton* Singleton::_instance=nullptr;
std::mutex Singleton::_mutex;

上面我们利用C++中提供的锁解决了多线程问题,但是如果每次访问都要加锁,并且多线程访问只有一个能够进去,其他要等待,性能非常不好

下面我们来利用双检测法来解决问题:

cpp 复制代码
//懒汉式:
//初始版本--3
#include <mutex>
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		//双检测法: 
		if(nullptr==_instance)
		{
			_mutex.lock();
			if(nullptr==_instance)
			{
				//创建对象 
				_instance =new Singleton;
			}
			_mutex.unlock();
		}
		return _instance;
	}
	//禁用拷贝构造和赋值运算符
	Singleton(const Singleton&) =delete;
	Singleton& operator=(const Singleton&)=delete;
priavte:
	//构造析构私有化:
	Singleton()
	{
	}
	~Singleton()
	{
	}
	static Singleton* _instance;//定义一个对象指针
	static std::mutex _mutex; 
};
//类对象实例化: 
Singleton* Singleton::_instance=nullptr;
std::mutex Singleton::_mutex;

该双检测法并非是正确的双检测法,原因:如果CPU执行new的指令发生问题,即如果先返回对象指针,这样就会接受到一个nullptr的指针,出现问题

newCPU执行过程;

1.分配空间 malloc

2.调用构造函数 (类)

3.返回对象指针

下面我们来学习正确的双检测法;

cpp 复制代码
//懒汉式:
//初始版本--4
#include <mutex>
#include <atmoic>
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		//双检测法: (正确写法)
		Singleton* tmp = _instance.load(std::memory_order_relaxed);//std::memory_order_relaxed---C++11 引入,最宽松的内存顺序约束,仅保证原子性,不提供线程间的同步或顺序保证
        std::atomic_thread_fence(std::memory_order_acquire); 
		if(nullptr==_instance)
		{
			_mutex.lock();
			tmp = _instance.load(std::memory_order_relaxed);
            if (tmp == nullptr) 
			{
                tmp = new Singleton();
                std::atomic_thread_fence(std::memory_order_release);
                _instance.store(tmp, std::memory_order_relaxed);
            }
			_mutex.unlock();
		}
		return tmp;
	}
	//禁用拷贝构造和赋值运算符
	Singleton(const Singleton&) =delete;
	Singleton& operator=(const Singleton&)=delete;
priavte:
	//构造析构私有化:
	Singleton()
	{
	}
	~Singleton()
	{
	}
	static std::atomic<Singleton*> _instance;;//定义一个对象指针
	static std::mutex _mutex; 
};
//类对象实例化: 
std::atomic<Singleton*> Singleton::_instance(nullptr);
std::mutex Singleton::_mutex;

利用Atomic来保证原子性,保证双检测不会受到CPU执行指令顺序影响

优点:

线程安全,高性能(只有第一次需要加锁)

缺点:

实现复杂,需要注意内存屏障

最后,我们来学习发明单例模式的作者是如何写的:

cpp 复制代码
//作者实现的: 
class Singleton 
{
public:
    static Singleton& getInstance() 
	{
        static Singleton instance;
        return instance;
    }
    
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    
private:
    Singleton() {}
    ~Singleton() {}
};

特点;

线程安全(C++11保证局部静态变量的线程安全初始化)

延迟初始化

简洁高效

不需要考虑内存释放问题

**(**只能说不愧是大佬!!!)

其实我们也可以考虑下智能指针和call_once来实现,大家可以试试

最后,感谢你的浏览,点个关注吧!!!

相关推荐
Logintern091 小时前
[Matlab] 遗传算法求解TSP入门
开发语言·matlab
练习时长两年半的RL练习生2 小时前
与AI对话后对 Actor-Critic 中 TD Target 的 a‘ 来源总结
开发语言·人工智能·php
青少儿编程课堂2 小时前
用图形化编程做一个“少年探险闯关”小游戏:方向键控制、碰撞检测与多关卡串起完整项目
c++·python·算法·bfs·信息学竞赛
liulilittle3 小时前
长上下文的成本结构与「甜点区间」——从推理引擎的物理约束看 200K/256K/400K
c++·人工智能·ai·llm·注意力·qkv
十五年专注C++开发3 小时前
100w条数据丝滑滚动!Qt Model/View 架构实战(二)
开发语言·c++·qt
萧瑟余晖3 小时前
Java深入解析篇三十九之集成测试
java·开发语言·集成测试
skr爱码士3 小时前
06_Qt 常用数据类型与容器:QString、QList、QVector、QMap 的性能与实现分析
开发语言·c++·qt
总有刁民想爱朕ha3 小时前
零基础Python开发「图片批量转MP4视频」工具,本地离线、免费无水印
开发语言·python·音视频
l1t3 小时前
测试DuckDB luajit插件读取本地通达信文件
开发语言·数据库·duckdb
老一岁4 小时前
c问题总结(2)
c语言·开发语言