文章目录
C++实现单例模式
1. 单例模式
单例模式,目的是确保一个类仅有一个实例化对象, 该实例被所有程序模块共享(提供一个全局访问点)
例如我们最常用的 腾讯视频, 计算器, 有道云笔记等软件都是默认单例模式, Windows的任务管理器和垃圾站都是典型的单例;
往小处说, 我们在做日志系统时, 我们只需要做一个全局日志读写对象, 不必要每次 new再 delete
单例模式的应用场景:
- 在一些需要大量消耗资源的情况下, 可以考用单例模式来减小维护开销;像任务管理器不断更新进程信息, 再比如数据库的连接, 都是比较耗资源的
定义一个单例类:
- 私有化构造函数, 防止外部调用创建
- 使用类的私有静态指针变量指向唯一实例
- 使用公有静态方法获取该实例
2. 懒汉式
懒汉式: 第一次使用的时候才会实例化
2.1 懒汉式——普通实现
#include <iostream>
using namespace std;
class CSingleton
{
private:
static CSingleton* pInstance;
private:
CSingleton():_num(0) {};
~CSingleton() {};
CSingleton(const CSingleton&) {};
CSingleton& operator=(const CSingleton&) {};
public:
void print() { std::cout << "_num:" << _num << std::endl; };
private:
int _num;
public:
// 定义公开方法提供一个全局访问点
static CSingleton* getInstPtr()
{
if (!pInstance)
{
pInstance = new CSingleton();
}
return pInstance;
}
// 定义公开方法销毁实例对象
static void DestroyInst()
{
if (pInstance)
{
delete pInstance;
}
}
/*
这里我们采用静态方法来销毁实例对象, 同时我们也能采用嵌套类的方式来实现实例对象自动销毁
我们将在下面做具体介绍, 有需要直接跳到 2.4 懒汉式——嵌套类实现
*/
};
// 设置静态变量
CSingleton* CSingleton::pInstance = NULL;
int main(int argc, char *argv[])
{
CSingleton* pInst = CSingleton::getInstPtr();
pInst->print();
CSingleton::DestroyInst();
return 0;
}
分析:
- 以上代码是比较典型的单例模式的实现方式, 但是并不是线程安全的, 在多线程的情况下,可能实例化多个对象
- 所以我们需要对代码进行一些线程安全的处理
2.2 懒汉式——处理线程安全
关于线程同步机制,这里采用 c++11特性 (std::lock_guard等),这里不做具体介绍,可自行百度
#include <iostream>
#include <mutex>
using namespace std;
// 定义一个线程同步的全局标识
std::mutex g_lock;
class CSingleton
{
private:
static CSingleton* pInstance;
private:
CSingleton():_num(0) {};
~CSingleton() {};
CSingleton(const CSingleton&) {};
CSingleton& operator=(const CSingleton&) {};
public:
void print() { std::cout << "_num:" << _num << std::endl; };
private:
int _num;
public:
// 定义公开方法提供一个全局访问点
static CSingleton* getInstPtr()
{
if (!pInstance)
{
lock_guard<std::mutex> locker(g_lock);
if (!pInstance)
{
pInstance = new CSingleton();
}
}
return pInstance;
}
// 定义公开方法销毁实例对象
static void DestroyInst()
{
if (pInstance)
{
delete pInstance;
}
}
};
// 设置静态变量
CSingleton* CSingleton::pInstance = NULL;
int main(int argc, char *argv[])
{
CSingleton* pInst = CSingleton::getInstPtr();
pInst->print();
CSingleton::DestroyInst();
return 0;
}
分析:
-
在全局访问点中, 采用“双检锁”机制, 避免因为加锁释放锁带来的性能和资源消耗, 我们对静态实例指针对两次判空,只需要在第一次实例化实例化对象时加锁处理线程安全问题, 在后续的获取该实例时不需要再做无为的加锁工作
-
这里已经可以满足一个类仅有一个实例对象的要求, 但是在对性能要求较高的情境下, 加锁的操作将会成为性能的瓶颈
ps: 作者在知乎看到有大神对这个线程原子性有不同的见解, 我也没有整明白, 暂时认定这样做没问题, 整清楚后再做分析
2.3 懒汉式——优化性能
#include <iostream>
using namespace std;
class CSingleton
{
private:
CSingleton() :_num(0) {};
~CSingleton() {};
CSingleton(const CSingleton&) {};
CSingleton& operator=(const CSingleton&) {};
public:
void print() { std::cout << "_num:" << _num << std::endl; };
private:
int _num;
public:
// 定义公开方法提供一个全局访问点
static CSingleton& getInstPtr()
{
// 静态局部变量是线程安全的
static CSingleton inst;
return inst;
}
};
int main(int argc, char *argv[])
{
CSingleton &inst = CSingleton::getInstPtr();
inst.print();
return 0;
}
C++11规定 局部静态变量是线程安全的; 由于定义静态实例对象, 不需要再考虑内存释放问题
2.4 懒汉式——嵌套类实现
在上述代码中,我们都对内存释放问题做了处理,由于析构函数声明为private,不能显示调用 delete进行内存释放,所以我们引入静态函数 DestroyInst()
作者在《Effective C++》中了解到一种嵌套类写法,也能有效做到内存释放
#include <iostream>
using namespace std;
class CSingleton
{
private:
static CSingleton* pInstance;
private:
CSingleton() :_num(0) {};
~CSingleton() {};
CSingleton(const CSingleton&) {};
CSingleton& operator=(const CSingleton&) {};
public:
void print() { std::cout << "_num:" << _num << std::endl; };
private:
int _num;
private:
// 定义 Deletor静态私有类对象实例
class Deletor {
public:
~Deletor()
{
if (CSingleton::pInstance)
{
delete CSingleton::pInstance;
}
}
};
static Deletor _deletor;
public:
// 定义公开方法提供一个全局访问点
static CSingleton* getInstPtr()
{
if (!pInstance)
{
pInstance = new CSingleton();
}
return pInstance;
}
};
// 设置静态变量
CSingleton* CSingleton::pInstance = NULL;
int main(int argc, char *argv[])
{
CSingleton* pInst = CSingleton::getInstPtr();
pInst->print();
return 0;
}
分析:
- 声明一个析构类,实例化私有静态对象,当程序结束时,自动调用静态对象的析构函数(~Deletor为 public),来释放单例对象
2. 饿汉式
饿汉式: 程序执行时立即初始化单例对象
2.1 饿汉式实现
#include <iostream>
using namespace std;
class CSingleton
{
private:
static const CSingleton *pInstance;
private:
CSingleton() {};
~CSingleton() {};
CSingleton(const CSingleton&) {};
CSingleton& operator=(const CSingleton&) {};
public:
// 定义公开方法提供一个全局访问点
static CSingleton* getInstPtr()
{
return const_cast<CSingleton*>(pInstance);
}
};
// 实例化静态单例对象
const CSingleton* CSingleton::pInstance = new CSingleton();
int main(int argc, char *argv[])
{
CSingleton* pInst = CSingleton::getInstPtr();
return 0;
}
分析:
- 首先关于内存的释放不再重复, 前面代码已经做了相关处理, 需要的可自行查看添加
- 在执行 main函数之前, 以单线程的方式完成对静态对象的实例化, 是线程安全的
ps: 还是在知乎上看到, 这里可能存在潜在问题, 待作者完全整明白再做刨析
3. 总结
- 在上述的代码中, 我们也以较多的笔墨介绍在单例模式下如何释放内存的问题;其实作者认为在实际开发中并不会在乎这个实例销毁的问题,因为整个程序共享这一个单例对象,当软件结束时单例类对象的生命周期也就结束了,然后所占用的资源也会被释放,并不会存在所谓的内存泄露问题。
- 当然为了代码的规范以及一些必须立即手动释放的资源,我们有必要做好内存的释放。
- 这句话是最重要的,由于c++11的支持,作者极度推荐使用 2.3中讲到的局部静态变量的单例模式实现版本,舒适优雅。