单例模式,目的是确保一个类仅有一个实例化对象, 该实例被所有程序模块共享(提供一个全局访问点)
例如我们最常用的 腾讯视频, 计算器, 有道云笔记等软件都是默认单例模式, Windows的任务管理器和垃圾站都是典型的单例;
往小处说, 我们在做日志系统时, 我们只需要做一个全局日志读写对象, 不必要每次 new再 delete
单例模式的应用场景:
定义一个单例类:
懒汉式: 第一次使用的时候才会实例化
#include
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;
}
分析:
关于线程同步机制,这里采用 c++11特性 (std::lock_guard等),这里不做具体介绍,可自行百度
#include
#include
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 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: 作者在知乎看到有大神对这个线程原子性有不同的见解, 我也没有整明白, 暂时认定这样做没问题, 整清楚后再做分析
#include
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规定 局部静态变量是线程安全的; 由于定义静态实例对象, 不需要再考虑内存释放问题
在上述代码中,我们都对内存释放问题做了处理,由于析构函数声明为private,不能显示调用 delete进行内存释放,所以我们引入静态函数 DestroyInst()
作者在《Effective C++》中了解到一种嵌套类写法,也能有效做到内存释放
#include
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;
}
分析:
饿汉式: 程序执行时立即初始化单例对象
#include
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(pInstance);
}
};
// 实例化静态单例对象
const CSingleton* CSingleton::pInstance = new CSingleton();
int main(int argc, char *argv[])
{
CSingleton* pInst = CSingleton::getInstPtr();
return 0;
}
分析:
ps: 还是在知乎上看到, 这里可能存在潜在问题, 待作者完全整明白再做刨析