单例模式是设计模式中常用的一种。在C++语言中,单例模式的写法也有讲究,
此文将列出几种实现方法,并进行简单讨论。

【单例类的定义】

  1. class Singleton{
  2. private: //把构造函数设置成私有,让外界不能创建它
  3. Singleton();
  4. Singleton(const Singleton& other);
  5. public:
  6. static Singleton* getInstance();
  7. static Singleton* m_instance;
  8. };
  9. Singleton* Singleton::m_instance=nullptr;

版本一:线程非安全

  1. //[版本一] 线程非安全版本
  2. Singleton* Singleton::getInstance() {
  3. if (m_instance == nullptr) { //语句一
  4. m_instance = new Singleton(); //语句二
  5. }
  6. return m_instance;
  7. }
  8. //假设对象还没有创建,m_instance现在为nullptr
  9. //线程A、线程B两个一起进入语句二,一起执行语句二
  10. //所以会导致,这个对象被创建多次,而真正被释放的只有一个

版本二:线程安全

  1. Singleton* Singleton::getInstance() {
  2. Lock lock; //加锁
  3. if (m_instance == nullptr) { //读操作
  4. m_instance = new Singleton(); //写操作
  5. }
  6. return m_instance; //读操作
  7. //退出以后,锁lock被释放,别的线程才能执行函数
  8. }
  9. //只有写操作需要上锁,读操作是不需要上锁的
  10. //所以,这个例子中,读操作也被上锁了。锁是有代价的,要等待别人完成,所以对读上锁,会造成浪费
  11. //故,在这个案例中,锁的代价过高
  12. //如果在高并发的场景下,这个代价是很高的

版本三:线程安全,并双检查锁

  1. //[版本三] 双检查锁(double check lock),但由于内存读写reorder不安全
  2. Singleton* Singleton::getInstance() {
  3. if(m_instance==nullptr){ //是否空
  4. Lock lock; //空才加锁
  5. if (m_instance == nullptr) { //加完锁之后,还需要判断是否为空
  6. //m_instance == nullptr判断是必要的!可以停下来想想为什么是必要的
  7. //其实如果没有这句,执行情况和版本二是一样的,也会被创建两次
  8. m_instance = new Singleton();
  9. }
  10. }
  11. return m_instance;
  12. }

笔者:李建忠前辈说“reorder不安全”,我不是很明白。有知道的朋友,一定要补充一下哦!

版本四:线程安全,跨平台

  1. //C++ 11版本之后的跨平台实现 (volatile)
  2. std::atomic<Singleton*> Singleton::m_instance;
  3. std::mutex Singleton::m_mutex;
  4. Singleton* Singleton::getInstance() {
  5. Singleton* tmp = m_instance.load(std::memory_order_relaxed);
  6. std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
  7. if (tmp == nullptr) {
  8. std::lock_guard<std::mutex> lock(m_mutex);
  9. tmp = m_instance.load(std::memory_order_relaxed);
  10. if (tmp == nullptr) {
  11. tmp = new Singleton;
  12. std::atomic_thread_fence(std::memory_order_release);//释放内存fence
  13. m_instance.store(tmp, std::memory_order_relaxed);
  14. }
  15. }
  16. return tmp;
  17. }

版本五:线程安全(std::call_once)

在地质算法工程中,用的是这种写法。

  1. #include<mutex> //std::call_once
  2. //多线程安全
  3. glal::ModelImporter* ModelImporter::getInstance()
  4. {
  5. static ModelImporter instance;
  6. //只执行一次初始化函数
  7. static std::once_flag oc;
  8. std::call_once(oc, &ModelImporter::_init, &instance);
  9. return &instance;
  10. }
  11. //初始化函数
  12. void ModelImporter::_init()
  13. {
  14. //初始化操作...
  15. }

如果初始化函数做的事情比较少,也可以用匿名函数,比如

  1. AlgoFactory* AlgoFactory::getInstance()
  2. {
  3. static AlgoFactory instance;
  4. static std::once_flag oc;
  5. std::call_once(oc, []()
  6. {
  7. //加载插件
  8. int successSize = dan::SGPluginManager::instance()->loadPluginsByFolder("", dan::StringList() << "glal.algo.provider.*.dll");
  9. });
  10. return &instance;
  11. }