单例模式是设计模式中常用的一种。在C++语言中,单例模式的写法也有讲究,
此文将列出几种实现方法,并进行简单讨论。
【单例类的定义】
class Singleton{
private: //把构造函数设置成私有,让外界不能创建它
Singleton();
Singleton(const Singleton& other);
public:
static Singleton* getInstance();
static Singleton* m_instance;
};
Singleton* Singleton::m_instance=nullptr;
版本一:线程非安全
//[版本一] 线程非安全版本
Singleton* Singleton::getInstance() {
if (m_instance == nullptr) { //语句一
m_instance = new Singleton(); //语句二
}
return m_instance;
}
//假设对象还没有创建,m_instance现在为nullptr
//线程A、线程B两个一起进入语句二,一起执行语句二
//所以会导致,这个对象被创建多次,而真正被释放的只有一个
版本二:线程安全
Singleton* Singleton::getInstance() {
Lock lock; //加锁
if (m_instance == nullptr) { //读操作
m_instance = new Singleton(); //写操作
}
return m_instance; //读操作
//退出以后,锁lock被释放,别的线程才能执行函数
}
//只有写操作需要上锁,读操作是不需要上锁的
//所以,这个例子中,读操作也被上锁了。锁是有代价的,要等待别人完成,所以对读上锁,会造成浪费
//故,在这个案例中,锁的代价过高
//如果在高并发的场景下,这个代价是很高的
版本三:线程安全,并双检查锁
//[版本三] 双检查锁(double check lock),但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
if(m_instance==nullptr){ //是否空
Lock lock; //空才加锁
if (m_instance == nullptr) { //加完锁之后,还需要判断是否为空
//m_instance == nullptr判断是必要的!可以停下来想想为什么是必要的
//其实如果没有这句,执行情况和版本二是一样的,也会被创建两次
m_instance = new Singleton();
}
}
return m_instance;
}
笔者:李建忠前辈说“reorder不安全”,我不是很明白。有知道的朋友,一定要补充一下哦!
版本四:线程安全,跨平台
//C++ 11版本之后的跨平台实现 (volatile)
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;
Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_release);//释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
版本五:线程安全(std::call_once)
在地质算法工程中,用的是这种写法。
#include<mutex> //std::call_once
//多线程安全
glal::ModelImporter* ModelImporter::getInstance()
{
static ModelImporter instance;
//只执行一次初始化函数
static std::once_flag oc;
std::call_once(oc, &ModelImporter::_init, &instance);
return &instance;
}
//初始化函数
void ModelImporter::_init()
{
//初始化操作...
}
如果初始化函数做的事情比较少,也可以用匿名函数,比如
AlgoFactory* AlgoFactory::getInstance()
{
static AlgoFactory instance;
static std::once_flag oc;
std::call_once(oc, []()
{
//加载插件
int successSize = dan::SGPluginManager::instance()->loadPluginsByFolder("", dan::StringList() << "glal.algo.provider.*.dll");
});
return &instance;
}