<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          談?wù)?C++ 單例模式

          共 2392字,需瀏覽 5分鐘

           ·

          2021-10-26 16:49

          單例模式是一個很常見的設(shè)計模式,也廣泛應(yīng)用于程序開發(fā)。其具有如下特點(diǎn):
          • 一個類只有一個實(shí)例化對象
          • 全局可以使用
          那么有人要問,那我不就定義一個類,程序只初始化一個全局的實(shí)例就好了嗎?沒錯,這樣是可以的。但是我們都知道程序會經(jīng)過多人的接手維護(hù)和開發(fā),比如第N個接手程序的時候,并不知道這個類定義的時候只能初始化一個實(shí)例,然后又實(shí)例化了新的對象, 則可能會造成意想不到的場景。那么這時候就要提到防御性編程,個人認(rèn)為單例模式的實(shí)現(xiàn)也是防御性編程的一種方式,讓這個類保證只有一個實(shí)例化對象,并且如果試圖構(gòu)造多個對象的時候,在程序的編譯期報錯。題外話,這也是為什么本人在進(jìn)行一些稍大規(guī)模開發(fā)的時候,只會去選擇強(qiáng)類型語言,而不會選擇弱類型語言的原因,強(qiáng)類型語言會在編譯期間幫我們避免很多運(yùn)行時可能產(chǎn)生的的Bug。
          本文我們將探討如下內(nèi)容:
          • 單例模式的基本實(shí)現(xiàn):包含單例模式的實(shí)現(xiàn),線程安全,以及生命周期等
          • 單例模式的模板實(shí)現(xiàn), 多模塊調(diào)用單例存在的問題


          單例模式的基本實(shí)現(xiàn)

          在程序開發(fā)中,比較常見的單例就是程序啟動的相關(guān)配置信息了。比如我們定義一個SingletonConfig類。注意這個類有如下特點(diǎn):
          • 私有的構(gòu)造函數(shù), 拷貝構(gòu)造函數(shù),以及operator=, 保證其不能夠在類的外部進(jìn)程對象構(gòu)造,拷貝等操作。
          • GetInstance是一個公有的靜態(tài)成員函數(shù),用來構(gòu)造這個類唯一的實(shí)例對象m_objConfig, 并且返回給使用者。
          我們來看下代碼實(shí)現(xiàn):
          class SingletonConfig{public:  static SingletonConfig * GetInstance(){    if (m_objConfig =  = nullptr)      m_objConfig = new SingletonConfig;    return m_objConfig;  }
          private: SingletonConfig() { ; }; SingletonConfig(const SingletonConfig&) { ; }; SingletonConfig& operator= (const SingletonConfig&) { ; }; private: static SingletonConfig *m_objConfig;};
          SingletonConfig* SingletonConfig::m_objConfig = nullptr;
          這也就是單例模式的基本實(shí)現(xiàn)了,然后我們需要考慮的就是單例的模式的生命周期問題,?單例的實(shí)例何時創(chuàng)建?何時銷毀?


          單例模式生命周期

          單例創(chuàng)建的時機(jī)

          根據(jù)單例的創(chuàng)建時間,可以分為餓漢模式懶漢模式
          上一節(jié)所展示的代碼則是懶漢模式: 當(dāng)你的應(yīng)用程序在需要調(diào)用GetInstance()方法的時候才創(chuàng)建實(shí)例化的對象,類似于懶加載。這種方式的好處在于,有一些單例模式的實(shí)例,可能在整個進(jìn)程的聲明周期內(nèi)可能不一定用到。那么這種懶漢模式就省去了不必要的資源創(chuàng)建過程。
          餓漢模式一般的實(shí)現(xiàn)方式為,在進(jìn)程或者模塊加載的時候就會創(chuàng)建全局的實(shí)例。比如將上述單例模式修改為。像這種進(jìn)程啟動必須要使用的單例對象,使用餓漢模式實(shí)現(xiàn)比較簡單。
          class SingletonConfig{public:  static SingletonConfig * GetInstance(){    return m_objConfig;  }
          private: SingletonConfig() { ; }; SingletonConfig(const SingletonConfig&) { ; }; SingletonConfig& operator= (const SingletonConfig&) { ; }; private: static SingletonConfig *m_objConfig;};
          SingletonConfig* SingletonConfig::m_objConfig = new SingletonConfig;
          綜合來看什么情況下該使用餓漢模式,什么情況下該使用懶漢模式呢?個人認(rèn)為大多數(shù)實(shí)現(xiàn)的場景下應(yīng)該使用懶漢模式,其更加靈活,可以自己定義單例對象的創(chuàng)建時間;對于初始化對象時間比較長的單例,可以在進(jìn)程啟動的時候手動的調(diào)用GetInstance()方法來完成初始化,避免在服務(wù)過程中導(dǎo)致第一個初始化示例對象的任務(wù)處理速度變慢。

          單例釋放的時機(jī)

          接下來查看,那么單例模式應(yīng)該何時釋放其資源呢?一般情況下當(dāng)進(jìn)程退出的時候,一般的資源也都會隨之釋放,大多數(shù)場景單例模式即使不手動去調(diào)用析構(gòu)函數(shù)也不會帶來很大的問題。但是有一些場景想在進(jìn)程退出前把資源處理完善,比如這個單例對象有內(nèi)存中的內(nèi)容需要刷新到磁盤。那么有兩種方法,一種是全局static對象由進(jìn)程退出的時候調(diào)用析構(gòu)函數(shù),另一種是讓單例使用者自己進(jìn)行析構(gòu)函數(shù)調(diào)用。
          先說說全局static對象,一種是直接在類成員里面定義一個static成員,或者是在GetInstance()中定義一個static單例對象,比如:
          class SingletonConfig{public:  static SingletonConfig * GetInstance(){    static SingletonConfig objConfig;    return &objConfig;  }  virtual ~SingletonConfig()  {    std::cout << "~SingletonConfig()" << std::endl;  }private:  SingletonConfig() { ; };  SingletonConfig(const SingletonConfig&) { ; };  SingletonConfig& operator= (const SingletonConfig&) { ; };};

          這種方法在程序退出的時候,將會調(diào)用SingletonConfig的析構(gòu)函數(shù),看下匯編,可以看到利用atexit注冊了一個方法,這個方法中會調(diào)用SingletonConfig的析構(gòu)函數(shù),并且在程序退出的時候執(zhí)行。

          如果想自己去控制單例模式的釋放時間可以實(shí)現(xiàn)如下, 在合適的時機(jī)調(diào)用ReleaseInstance方法去釋放單例對象。

          class SingletonConfig{public:  static SingletonConfig * GetInstance(){    if (m_objConfig == nullptr)      m_objConfig = new SingletonConfig;    return m_objConfig;  }
          static void ReleaseInstance(){ if (m_objConfig) { delete m_objConfig; m_objConfig = nullptr; } }
          virtual ~SingletonConfig() { std::cout << "~SingletonConfig()" << std::endl; }private: SingletonConfig() { ; }; SingletonConfig(const SingletonConfig&) { ; }; SingletonConfig& operator= (const SingletonConfig&) { ; };
          private: static SingletonConfig* m_objConfig;};SingletonConfig* SingletonConfig::m_objConfig = nullptr;
          這里我要留一個問題給讀者如果有兩個單例模式SingletonASingletonB, 他們都采用static的方式實(shí)現(xiàn)單例,那么如果SingletonA調(diào)用了SingletonB,有沒有可能產(chǎn)生什么問題?如果有如何避免這個問題?如果不知道的可以看看書籍**<>中的鳳凰單例,意味著涅槃重生。** 如果對這個理解了,那對單例模式的生命周期的理解也差不多到位了。


          線程安全

          如果是如下方式使用static對象方式實(shí)現(xiàn)的單例模式,在C++ 11之前是非線程安全的,而在C++ 11之后是線程安全的。
          static SingletonConfig * GetInstance(){  static SingletonConfig objConfig;  return &objConfig;}
          但如果不使用static對象,采用下述方式,那么在單例對象還沒初始化的時候,當(dāng)多線程同時調(diào)用GetInstance可能會出現(xiàn)線程安全問題,導(dǎo)致創(chuàng)建了多個SingletonConfig
          static SingletonConfig * GetInstance(){  if (m_objConfig == nullptr)    m_objConfig = new SingletonConfig;  return m_objConfig;}
          而一般的實(shí)現(xiàn)如下:
          • 使用std::lock_guard去多線程保證互斥
          • 雙重的m_objConfig == nullptr檢查,第一次是為了效率,當(dāng)單例對象已經(jīng)在的時候,就不需要互斥鎖了;第二次是進(jìn)入鎖范圍之后,要查看下,是否有其他線程已經(jīng)創(chuàng)建了單例對象,如果還沒有創(chuàng)建才進(jìn)行創(chuàng)建。
          class SingletonConfig{public:  static SingletonConfig * GetInstance(){    if (m_objConfig == nullptr)    {      std::lock_guard<std::mutex> guard(m_mutex);      if (m_objConfig == nullptr)      {        m_objConfig = new SingletonConfig;      }    }    return m_objConfig;  }
          static void ReleaseInstance(){ if (m_objConfig) { delete m_objConfig; m_objConfig = nullptr; } }
          virtual ~SingletonConfig() { std::cout << "~SingletonConfig()" << std::endl; }private: SingletonConfig() { ; }; SingletonConfig(const SingletonConfig&) { ; }; SingletonConfig& operator= (const SingletonConfig&) { ; };
          private: static SingletonConfig* m_objConfig; static std::mutex m_mutex;};SingletonConfig* SingletonConfig::m_objConfig = nullptr;std::mutex SingletonConfig::m_mutex;


          單例模式的模板實(shí)現(xiàn)以及可能的問題

          在網(wǎng)上或者一些書上,會使用模板去實(shí)現(xiàn)通用的單例模式,大致如下:
          template<typename T>class  CommonSingleton{public:  static T* GetInstance(){    if (m_objSingle == nullptr)    {      std::lock_guard<std::mutex> guard(m_mutex);      if (m_objSingle == nullptr)      {        m_objSingle = new T;      }    }    return m_objSingle;  }
          static void ReleaseInstance(){ if (m_objSingle) { delete m_objSingle; m_objSingle = nullptr; } }
          private: CommonSingleton() { ; }; CommonSingleton(const CommonSingleton&) { ; }; CommonSingleton& operator= (const CommonSingleton&) { ; };
          private: static T* m_objSingle; static std::mutex m_mutex;};
          template<typename T>T* CommonSingleton::m_objSingle = nullptr;
          template<typename T>std::mutex CommonSingleton::m_mutex;
          如果有一個類需要單例模式,比如TestClass, 則調(diào)用方式如下:
          CommonSingleton<TestClass>::GetInstance();
          以上的模板實(shí)現(xiàn)大家注意到了沒,這個實(shí)例化的對象有一個沒有參數(shù)的構(gòu)造函數(shù),如果一個類是必須有參數(shù)的構(gòu)造函數(shù)呢?這個時候其實(shí)可以借助C++ 11 中的可變參數(shù)的完美轉(zhuǎn)發(fā)。具體實(shí)現(xiàn)讀者可以思考下,如果不清楚的可以參考?《深入應(yīng)用C++11代碼優(yōu)化及工程級應(yīng)用》中的改進(jìn)單例模式這一章節(jié)
          不過本人認(rèn)為這一種的模板化實(shí)現(xiàn),并不是一個特別好的方案,我也并不會優(yōu)先選擇模板化的單例模式實(shí)現(xiàn),主要有兩點(diǎn)原因:
          1. 模板參數(shù)接受的類,可以是這種:默認(rèn)暴露給用戶,可以構(gòu)造,拷貝,賦值的類,這樣便可以重新創(chuàng)造多個對象。這種方式缺乏了本人所理解的防御性編程的思路。
          2. 當(dāng)使用模板實(shí)例化的時候,同一種模板參數(shù)的類,在多個不同的模塊中其實(shí)都會有自己的實(shí)例化對象。比如有A和B兩個模塊,并且均調(diào)用了CommonSingleton::GetInstance();, 其實(shí)在A和B中存在不同的TestClass對象,這樣也違背了一個程序一個實(shí)例化對象的初衷。當(dāng)然只有一個工程不影響。對于非模板的實(shí)現(xiàn),一般將單例實(shí)現(xiàn)的類從模塊導(dǎo)出,將實(shí)現(xiàn)放在.cpp文件中,那么這種多個工程對同一種單例的類只會有一個實(shí)例化對象。個人覺得這一點(diǎn)比較重要,需要讀者多多體會。


          總結(jié)

          單例模式除了其具有程序中單個實(shí)例化對象的特點(diǎn),也具有防御式編程的思想在其中。使用中一定要注意單例模式的生命周期,以及模板實(shí)現(xiàn)的跨模塊調(diào)用的問題。以上僅是一家之言,歡迎一起討論。


          參考

          1. <>中的Singletons實(shí)作技術(shù)這一章節(jié)
          2. <<深入應(yīng)用C++11代碼優(yōu)化及工程級應(yīng)用>>改進(jìn)單例模式這一章節(jié)
          瀏覽 38
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  给我播放欧洲成人在线 | 依依成人在线 | 亚洲AV无码久久蜜桃杨思敏 | 午夜丁香网 | 亚洲欧美性爱在线观看 |