一.定义
单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点
特点:
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来实现,大家可以试试
最后,感谢你的浏览,点个关注吧!!!