单例模式
在介绍单例模式前,我们先来理解一下设计模式的概念以及使用设计模式的目的:
- 设计模式:就是一套被反复利用、众人知晓、经过分类的、代码设计经验的总结。
- 使用其目的:就是为了代码的可重用性、让代码更容易被他人理解、保证代码可靠性。
作为设计模式的一种,单例模式自然也具有以上特点,那么现在我们就来说一下单例模式的概念及作用:
- 单例模式:即一个类只能创建一个对象,
- 作用:保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
我们可以这样来理解:假设在一个服务器程序中,服务器的配置信息存放在一个文件中,由一个单例对象来统一读取这些配置数据,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这样子就可以简化在复杂环境下的配置管理。
单例模式有两种实现方式:
1.饿汉模式
通俗一点来说,饿汉模式就是:不管以后用不用,程序在启动时就创建一个唯一的实例对象。
class Singleton
{
public:
static Singleton* GetInstance()
{
return &m_instance;
}
private:
// 构造函数私有
Singleton()
{}
// C++98防拷贝
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
// C++11防拷贝
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton m_instance;
};
Singleton Singleton::m_instance; // 在程序入口之前就完成单例对象的初始化
由此我们可以看到饿汉模式的优缺点:
- 优点:
1.实现简单
2.如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么明显使用饿汉模式来避免资源竞争,提高响应速度效果更好;- 缺点:
1.可能会导致进程启动慢,
2.如果有多个单例,类对象实例启动顺序不确定。
2.懒汉模式
了解了上面的饿汉模式,知道了其缺点,再看懒汉、饿汉模式两者的名字,我们便可以对其略知一二。
如果单例对象构造非常耗时或者占用很多资源,如:加载插件、初始化网络连接、读取文件等,而可能该对象在程序运行时并不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。因此这种情况下使用懒汉模式(延迟加载)的效果更好。
#include <iostream>
#include <mutex>
#include <thread>
class Singleton
{
public:
static Singleton* GetInstance()
{
// 注意这里一定要使用 Double-Check 的方式加锁,才能保证效率和线程安全
if (nullptr == m_pInstance)
{
m_mtx.lock();
if (nullptr == m_pInstance)
{
m_pInstance = new Singleton;
}
m_mtx.unlock();
}
return m_pInstance;
}
// 实现一个内嵌的垃圾回收类
class CGarbo
{
public:
~CGarbo()
{
if (Singleton::m_pInstance)
delete Singleton::m_pInstance;
}
};
// 定义一个静态的成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
static CGarbo Garbo;
private:
// 构造函数私有
Singleton()
{}
// 防拷贝
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
static Singleton* m_pInstance; // 单例对象指针
static mutex m_mtx; // 互斥锁
};
Singleton* Singleton::m_pInstance = nullptr;
Singleton::CGarbo Garbo;
mutex Singleton::m_mtx;
void Func(int n)
{
cout << Singleton::GetInstance() << endl;
}
// 多线程环境下演示上面 GetInstance() 加锁和不加锁的区别
int main()
{
thread t1(Func, 10);
thread t2(Func, 10);
t1.join();
t2.join();
cout << Singleton::GetInstance() << endl;
cout << Singleton::GetInstance() << endl;
return 0;
}
由此我们可以看到懒汉模式的优缺点:
- 优点:
1.在第一次使用实例对象时,创建对象,进程启动无负载,
2.多个单例实例启动顺序自由控制。- 缺点:
1.实现复杂。