您好,登錄后才能下訂單哦!
設計模式描述了對象如何進行通信才能不牽涉相互的數據模型和方法。 保持這種獨立性一直是一個好的面向對象程序設計的目標。 Gang of Four的“Design Patterns: Elements of Resualbel Software”書將設計模式 歸納為三大類型,共23種。 創建型模式 : 通常和對象的創建有關,涉及到對象實例化的方式。(共5種模式) 行為型模式: 通常和對象間通信有關。(共11種模式) 結構型模式: 描述的是如何組合類和對象以獲得更大的結構。(共7種模式) 類模式描述的是如何使用繼承提供更有用的程序接口。 而對象模式描述的是如何通過使用對象組合或將對象包含在其他對象里, 將對象組合成更大的一個結構。
1、單例模式可以保證:在一個應用程序中,一個類有且只有一個實例, 并提供一個訪問它的全局訪問點。 2、在程序設計過程中,有很多情況需要確保一個類只有一個實例。 例如: windows系統中只能有一個窗口管理器 某個程序中只能有一個日志輸出系統 一個GUI類庫中,有且只有一個ImageManager 還有其他無數種情況
1、優點:該實現是一個"懶漢"單例模式,意味著只有在第一次調用GetInstance(),靜 態方法的時候才進行內存分配。如果整個程序不調用該靜態方法,則不會分配內存。相對應的是"餓漢"單例模式。 2、缺點: 1) "懶漢"模式雖然有優點,但是每次調用GetInstance()靜態方法時, 必須判斷NULL == m_instance,使程序相對開銷增大。 2) 由于使用指針動態內存分配,我們必須在程序結束時, 手動的調用ReleaseInstance()靜態方法,進行內存的釋放。 3) 教科書標準實現最大的缺點是線程不安全。 根據該模式的定義,整個應用程序中,不管是單線程,還是多線程, 都只能有且只有該類的一個實例。而在多線程中會導致多個實例的產生, 從而導致運行代碼不正確以及內存的泄露。
1、我們創建3個輔助線程,外加main主線程,一共有4個線程。 2、我們在每個輔助線程里面調用GetInstance()靜態方法,由于每個線程 回調函數速度非常快,導致每個線程在判斷NULL==m_instance時, 都返回true,從而導致每個線程回調函數都會創建一個CSingleton1對 象并返回指向該對象的指針。 3、我們根本沒辦法進行CSingleton1的內存釋放,因為在多線程中, 我們根本不知道是創建了1個、2個或3個CSingleton1的實例
Meyers Singleton Pattern的優缺點 :
1、優點: 1) 該實現是一個"懶漢"單例模式,意味著只有在第一次調用GetInstance()時才會 實例化。 2) 不需要每次調用GetInstance()靜態方法時,必須判斷NULL==m_instance,效 率相對高一些。 3) 使用對象而不是指針分配內存,因此自動回調用析構函數,不會導致內存泄露。 4) 在多線程下的確能夠保證有且只有一個實例產生。 2、缺點: 在某些編譯器中,在多線程情況下,并不是真正意義上的線程安全的實現
我們修改一下前面線程函數
這是因為C++中構造函數并不是線程安全的。 C++中的構造函數簡單來說分兩步: 第一步:內存分配 第二步:初始化成員變量 由于多線程的關系,可能當我們在分配內存好了以后,還沒來得急初始化成員變量,就 進行線程切換,另外一個線程拿到所有權后,由于內存已經分配了,但是變量初始化還 沒進行,因此打印成員變量的相關值會發生不一致現象。 結論:Meyers方式雖然能確保在多線程中產生唯一的實例,但是不能確保成員變量的值是否正確.
1) 是一個"懶漢"單例模式,按需內存分配。 2) 基于模板實現,具有很強的通用性。 3) 自動內存析構,不存在內存泄露問題(使用std::tr1::shared_ptr)。 4) 在多線程情況下,是線程安全的。 5) 盡可能的高效。(線程安全必定涉及到線程同步,線程同步分為內核級別和用戶級別的 同步對象,用戶級別效率遠高于內核級別的同步對象,而用戶級別效率最高的是 InterlockedXXXX系列API)。 6) 這個實際上也是一個Double-Checked Locking實現的單例模式。是傳統的Double- Checked-Locking變異版本。
線程安全的單例模式(基礎版)的測試
線程安全的單例模式(基礎版)存在的不足
單例的一個原則就是禁止構造函數和析構函數為public,防止外部實例化,僅允許調用GetInstance()等靜態方法進行初始化。
由于使用模板技術,如果我們不將基類和子類的構造和析構函數設置為public級別,模板實例化導致編譯器報錯。
1、修正構造函數:
1) 將基類CSingletonPtr的構造函數為protected訪問級別。
2) 每一個繼承自CSingletonPtr的子類也將構造函數聲明為protected訪問級別,并在繼 承類中聲明友元類。
3) 在上述代碼設定以后,我們會發現對于構造函數,通過子類授權給基類的方式,我們 能夠很順利的通過編譯,代碼正確的運行。
這樣我們解決了防止第三方調用Manager的構造函數,Manager類的構造函數只允許在
GetInstance()靜態方法中被調用。
2、修正析構函數:
我們會發現,對于析構函數,它依舊是public訪問級別的,為什么不讓析構函數也聲明為
protected級別呢?
因為由于我們使用了std::tr1::shared_ptr(與boost::shared_ptr基本一致),Manager類的析構委托給了shared_ptr ,而Manager授權給的是其基類。所以如果我們將析構函數設置為protected級別,編譯器會報錯。
那么我們第一個反應就是我們繼續在Manager中授權shared_ptr。但是沒成功。可能是由于shared_ptr實現的機制導致不能成功。
難道我們真的沒有辦法將析構函數修正為受保護級別嗎?
1) 在基類CSingletonPtr中聲明并實現一個訪問級別為private的嵌套類Deleter,代表一個刪除器。 重載函數調用操作符,該刪除器類實際是一個仿函數對象。
2) 在GetInstance()靜態函數中增加刪除器設置代碼,見圖紅色部分。 一旦我們設置好刪除器,那么在shared_ptr析構時不會直接調用delete而是調用刪除器。 這樣我們 就將子類T的析構函數隱藏起來,不被外部調用。 3) 每一個繼承自CSingletonPtr的子類也將構造函數聲明為protected訪問級別,但不需 要聲明授權友元類。
通過上述步驟,我們將基類和子類的構造函數都聲明為受保護級別,以防止外部調用。這樣整個單例子類的生命周期都由shared_ptr控制。
3、修正GetInstance()靜態方法:
基礎版的GetInstance()靜態方法返回的是tr1::shared_ptr結構,這樣導致每次調用
GetInstance()都會使shared_ptr的引用計數加1并調用shared_ptr的拷貝構造函數。在
調用完成GetInstance()->Print方法后,又將臨時產生的shared_ptr對象引用計數減1,
這樣對效率有非常大的影響。
我們要避免這種情況,那么我們要做的是修改代碼,直接在GetInstance()中返回T的引用。
更進一步,我們使用編譯器提供的本質函數。
1、優點:該實現是一個"懶漢"單例模式,意味著只有在第一次調用GetInstance() 靜態方法的時候才進行內存分配。 通過模板和繼承方式,獲得了足夠通用的能力。 在創建單例實例的時候,具有線程安全性。 通過智能指針方式,防止內存泄露。 具有相對的高效性。 2、 缺點:肯定沒有單線程版本的效率高。 每個子類必須要授權基類,我們可以寫一個宏減少輸入: #define DECLARE_SINGLETON_CLASS(type) \ friend class CSingletonPtr <type>;
餓漢模式意味著在主線程(main函數代表主線程)之前就對類進行內存分配和初始化。實現代碼如下:
從編譯器以及是否線程安全方面考慮:
1、如果你使用vc6編譯器,請放棄設計模式。 2、如果你整個程序是單線程的,那么標準模式或Meyers單例模式是你最佳選擇。 3、如果你使用符合C++0X標準的編譯器的話,由于C++0X標準規定:要求編譯器保證內 部靜態變量的線程安全性。 目前只有VS2015支持內部靜態變量的線程安全性,因此Meyers單例模式是你最佳選擇。 4、如果你使用VC6以后,vc2010以下版本的編譯器的話,并且需要線程安全,則使用實現的Double-Checked-Locking版本的單件模式。
從單例模式實現的角度考慮:
1、總是避免第三方調用拷貝構造函數以及賦值操作符 2、總是避免第三方調用構造函數 3、盡量避免第三方調用析構函數 4、總是需要一個靜態方法用于全局訪問
本篇文檔寫于2010年,當時還是以vs2008為主,因此并沒有符合c++0x標準。現如今都vs2015了,c++11標準都普及了,因此Meyers單例模式是你最佳選擇。
不過關于上面的shared_ptr方面的應用,還是很有價值的。shared_ptr已成為目前c++內存操作的主流技術。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。