单例模式是设计模式中常用的一种。在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);//获取内存fenceif (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);//释放内存fencem_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;}
