单例模式 【饿汉式、懒汉式、线程安全、单例资源释放】

单例模式,保证整个工程中,有且仅有一个该类的实例对象。

一、饿汉式单例

二、懒汉式单例

三、创建单例的线程安全

   多线程情景中创建单例, 单例类的静态单例对象数据成员此时作为共享数据,那么势必有必要保证 获取单例的函数是线程安全的。 这里通过使用C++11新标准版本的线程库函数,来完成对线程安全的单例获取函数的编写。分两步改造,一个改版,以及一句总结 分享给大家。

///< Single.h-----------------------------

//仅仅针对 static  Single * instance() 函数的编写, 其他的忽略, 请阅读其他小节内容。

//线程安全, 保护共享数据,毫无疑问,应采用 “加锁” , 这里使用 互斥量。

======  第一版  ======
public:
static  Single * instance() {
       std::unique_lock<std::mutex>  guardLock ( m_mutex);   ///< 【核心点1】确保同一时刻,仅有一个线程能够执行
       if  (m_instance  ==  nullptr) {
              m_instance  =  new Single();
              static AutoClearSingle  autoClearSingle;
       }
        
       return m_instance;    
}

private:
       std::mutex  m_mutex;    

第一版问题点: 每次获取单例对象, 都需要等待锁, 而实际上,m_instance 为 nullptr,仅第一次获取单例对象时成立。 假如线程很多, 获取单例对象的调用次数频繁,这将极大的降低代码的执行效率。

======  第二版  ======
public:
static  Single * instance() {
       if  (m_instance  ==  nullptr) {   ///<【核心点1】  双重检查  (又叫:双重锁定)

              std::unique_lock<std::mutex>  guardLock ( m_mutex);    ///<【核心点2】
              if  (m_instance  ==  nullptr) {
                     m_instance  =  new Single();
                     static AutoClearSingle  autoClearSingle;
              }
       
       }

        
       return m_instance;    
}

private:
       std::mutex  m_mutex;  

解析: 多出的第一个判断  if  (m_instance  ==  nullptr)
如果  m_instance  !=  nullptr , 条件成立, 则肯定 m_instance 被 new 过了 。
如果  m_instance  == nullptr , 不一定代表, m_instance一定没被 new  过。 因此需要上锁。
简简单单的多一行双重检查机制, 能够大大的提高效率, 同时有能保证线程安全。


======  改版  ======
public:
       static  Single * instance() {
              std::call_once (m_call_once_flag,  createInstance);   ///< 【核心点1】
              return m_instance;
       }

private:
       static  void  createInstance() {
              m_instance  =  new  Single();
              static  AutoClearSingle  autoClearSingle;
       }

       std::once_flag  m_call_once_flag;    ///< 【核心点2】 标记 std::call_once 传入的可调用对象,是否被调用过。

备注:createInstance函数可以使用  lambda  代替, 让代码更简洁。
解析:
std::call_once 是 C++11 引入的新函数。 能够保证传入的可调用对象,只被调用一次。std::call_once 具备互斥量的能力, 而且传言在效率上比互斥量消耗更少的资源。


======  总结  ======
大道至简:  在子线程中访问获取单例对象函数,需要保证线程安全。 那么,真实开发时,如非迫不得已, 在主线程中,创建子线程之前, 先调用一次获取单例对象的函数, 所有的麻烦事都会烟消云散。

四、单例对象的资源释放

   编写单例类时,往往会忽视对单例对象资源的释放。这里采用 “静态对象只会创建一次,在程序退出时自动销毁 ” 以及 “ 内部类 ” 两个思想,分享一种精致的设计方案,。

///<  Single.h------------------------------------

class Single
{
   private: 
                 Single() {}      ///<私有化构造函数,杜绝内外创建类对象。
                 static  Single *  m_instance;     ///<静态成员

   public:
                 static Single * instance();
                 
                 class AutoClearSingle   ///<内部类, 用来释放单例对象  【核心点1】
                 {
                       public: 
                                    ~AutoClearSingle() {
                                        if ( Single::m_instance) {
                                                   delete  Single::m_instance;
                                                   Single::m_instance  =  nullptr;
                                        }
                                    }
                 };   ///< class AutoClearSingle
};    ///< class Single



///<  Single.cpp----------------------------------

Single * Single::m_instance  =  nullptr; 

Single * Single::instance()
{
        if (m_instance == nullptr) {
                m_instance  =  new  Single();
                static AutoClearSingle  autoClearSingle;    ///<  【核心点2】
        }
         
        return m_instance;
}

。。。。看看曾经写的一个单例随笔, 现在回头看,想法真是奇怪的很。。哈哈哈。。。 单例模式 代码例子

原文地址:https://www.cnblogs.com/azbane/p/13525688.html