C++深入詳解單例模式與特殊類設(shè)計(jì)的實(shí)現(xiàn)
單例模式
什么是單例模式
單例模式(Singleton),保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)。–大話設(shè)計(jì)模式
應(yīng)用場(chǎng)景
保證一個(gè)類只有一個(gè)實(shí)例
- 如Windows下的任務(wù)管理器,回收站等。
- 日志管理,計(jì)數(shù)器等。
簡(jiǎn)而言之,你需要唯一實(shí)例時(shí)就可以考慮單例模式。這樣它可以嚴(yán)格地控制客戶怎樣訪問即何時(shí)訪問它,即對(duì)唯一實(shí)例的受控訪問。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 減少內(nèi)存開銷,因?yàn)樵谙到y(tǒng)中只有一個(gè)實(shí)例。
- 避免了頻繁的創(chuàng)建和銷毀對(duì)象,提高了性能
- 避免對(duì)資源的多重占用,比如單例時(shí)多人只寫一個(gè)日志文件,如果有多個(gè)日志文件可能導(dǎo)致對(duì)相同的日志文件進(jìn)行寫操作
- 設(shè)置全局訪問點(diǎn)
缺點(diǎn)
- 職責(zé)過重,與單一職責(zé)存在沖突
- 不能繼承(構(gòu)造方法私有)
實(shí)現(xiàn)
單例模式有兩種實(shí)現(xiàn)模式,懶漢模式和餓漢模式。
注意單例模式的概念,大概就是唯一實(shí)例且有全局訪問點(diǎn),我們從這兩點(diǎn)入手。
唯一實(shí)例:構(gòu)造函數(shù)私有+防拷貝
拷貝構(gòu)造是一種構(gòu)造方式,所以需要防止,構(gòu)造函數(shù)私有不讓別人new。
全局訪問點(diǎn):給一個(gè)公共的接口
餓漢模式
簡(jiǎn)單來說,把東西一開始就做好,需要的時(shí)候直接吃。
#include <iostream> using namespace std; class Singleton { public: static Singleton& GetInstance() { return _instance; } int GetRandom()//由于側(cè)重點(diǎn)不是隨機(jī)數(shù) 所以直接返回一個(gè)30 { return 30; } private: Singleton() {}//構(gòu)造函數(shù)私有 Singleton(Singleton&) = delete;//防拷貝,被=delete修飾表明這個(gè)函數(shù)被刪除,即可以只聲明不實(shí)現(xiàn),換言之禁用了該函數(shù) Singleton& operator=(const Singleton&) = delete;//防拷貝 static Singleton _instance; }; Singleton Singleton::_instance;//類外必須初始化,類內(nèi)只是聲明 int main() { //1.調(diào)用 cout<<Singleton::GetInstance().GetRandom()<<endl; //2. Singleton& s = Singleton::GetInstance(); cout<<s.GetRandom()<<endl; return 0; }
通過防拷貝和構(gòu)造函數(shù)私有化之后下面的幾種辦法都失效了
Singleton s;//err Singleton s1(s);//err Singleton s1=s;//err
從上面我們可以看出餓漢模式的優(yōu)缺點(diǎn)了,有點(diǎn)顯而易見就是實(shí)現(xiàn)很簡(jiǎn)單粗暴,缺點(diǎn)很明顯,類加載時(shí)單例對(duì)象就已經(jīng)生成了,即還沒有用就已經(jīng)加載出來了,比如這個(gè)資源很大,又在游戲啟動(dòng)時(shí)加載,那就會(huì)造成游戲啟動(dòng)很慢。且如果有多個(gè)單例對(duì)象啟動(dòng)時(shí)實(shí)例化順序不確定(不同源文件類內(nèi)的單例對(duì)象實(shí)例化順序是不確定的,懶漢模式解決了這個(gè)問題,因?yàn)閼袧h模式的實(shí)例化在函數(shù)內(nèi)部,可以通過調(diào)用函數(shù)的順序來解決實(shí)例化的順序問題)。
類加載時(shí)靜態(tài)初始化解決了線程安全問題。
懶漢模式
什么時(shí)候需要就什么時(shí)候做飯,然后吃。
class Singleton { public: static Singleton* GetInstance() { if (_instance == nullptr) { _mtx.lock();//double lock保證線程安全 if (_instance == nullptr)//必須再次檢查 不然可能另一個(gè)線程那已經(jīng)new完了,這邊又new違背了單例,且可能覆蓋數(shù)據(jù)。 { _instance = new Singleton(); } _mtx.unlock(); } return _instance; } int GetRandom() { return 30; } class Clear//資源回收的內(nèi)部類,必須是公有的,不然外部聲明報(bào)錯(cuò) { public: ~Clear() { cout << "釋放資源" << endl; delete _instance; } }; private: static Clear _cle; Singleton() {} Singleton(Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; static Singleton* _instance; static mutex _mtx;//線程安全 }; Singleton* Singleton::_instance = nullptr; mutex Singleton::_mtx; Singleton::Clear _cle; int main() { //1. cout<<Singleton::GetInstance()->GetRandom()<<endl; //2/ static Singleton* s = Singleton::GetInstance(); cout << s->GetRandom() << endl; //不能用左值引用接收的原因 返回值是右值 得用右值引用接收 //為什么函數(shù)返回值是右值 因?yàn)榉祷刂凳墙柚R時(shí)變量返回的拿不到地址 //如果返回值是左值引用就能用左值引用接收 return 0; }
懶漢模式的優(yōu)缺點(diǎn)也很明顯,優(yōu)點(diǎn)是什么時(shí)候第一次用就什么時(shí)候?qū)嵗?,此外可以通過調(diào)用函數(shù)解決多個(gè)單例對(duì)象實(shí)例化的順序問題,缺點(diǎn)就是寫起來復(fù)雜,要考慮線程安全和內(nèi)存泄露方面的問題。懶漢采用的是指針也更好回收資源(餓漢采用的是對(duì)象)
懶漢可能因?yàn)槎嗑€程丟數(shù)據(jù),線程加鎖保證在多線程環(huán)境下一定只有一個(gè)線程去new對(duì)象,只創(chuàng)建出一個(gè)單例對(duì)象,加鎖可能導(dǎo)致頻繁切換上下文,double lock解決。
特殊類設(shè)計(jì)
我們一般會(huì)通過構(gòu)造函數(shù),拷貝構(gòu)造和賦值重載創(chuàng)建對(duì)象,現(xiàn)在把這幾種方法全禁用了,然后自己再寫一個(gè)外部可調(diào)用的接口來自定義類的創(chuàng)建方式。
當(dāng)然也有別的辦法,這種經(jīng)??梢宰鳛橐环N通解思路。
下面的禁用采用C++11的delete關(guān)鍵字實(shí)現(xiàn),作用是禁止編譯器生成默認(rèn)的函數(shù)版本,即有聲明但無實(shí)現(xiàn)。
設(shè)計(jì)一個(gè)類只能在堆上創(chuàng)建對(duì)象
簡(jiǎn)單來說只能通過new來創(chuàng)建對(duì)象。
方法一
構(gòu)造方法禁用,然后給一個(gè)接口讓外部調(diào)用。
構(gòu)造方法的禁用=構(gòu)造函數(shù)私有+禁用拷貝構(gòu)造+禁用賦值重載
class HeapOnly { public: static HeapOnly* GetInstance() { return new HeapOnly; } void Test() { cout << "I am Test" << endl; } private: HeapOnly() {} HeapOnly(HeapOnly&) = delete; HeapOnly& operator=(const HeapOnly&) = delete; }; int main() { HeapOnly* ho = HeapOnly::GetInstance(); ho->Test(); delete ho; return 0; }
方法二
析構(gòu)函數(shù)私有
對(duì)象建立在棧上,是由編譯器分配空間的,編譯器管理對(duì)象的生命周期,對(duì)象使用完后編譯器會(huì)檢查這個(gè)對(duì)象所有的非靜態(tài)函數(shù),包括析構(gòu)函數(shù),當(dāng)編譯器發(fā)現(xiàn)析構(gòu)函數(shù)不能訪問后就不能回收這塊空間,所以編譯器無法為其分配空間,編譯器檢查到這種情況也會(huì)報(bào)錯(cuò)。
析構(gòu)函數(shù)私有的方法不建議使用,因?yàn)樵陬愅鉄o法使用delete釋放空間,容易造成內(nèi)存泄漏。
class HeapOnly { public: void Test() { cout << "I am Test" << endl; } private: ~HeapOnly(); }; int main() { //HeapOnly ho_stack;//err HeapOnly* ho = new HeapOnly; ho->Test(); return 0; }
只能在棧上創(chuàng)建對(duì)象
方法一
不能使用new --> 重載operator new即可。
這種存在一個(gè)缺陷,就是仍然可以在靜態(tài)區(qū)創(chuàng)建對(duì)象
方法二
將構(gòu)造函數(shù)設(shè)為私有再自定義一個(gè)接口
這里不用禁用構(gòu)造函數(shù),拷貝構(gòu)造,賦值重載,因?yàn)閷?duì)于下面的場(chǎng)景來說,匿名對(duì)象的拷貝構(gòu)造更符合場(chǎng)景,編譯器會(huì)選拷貝構(gòu)造來進(jìn)行構(gòu)造,所以如果我們禁用了拷貝構(gòu)造會(huì)報(bào)錯(cuò),因?yàn)槲覀兊膁elete關(guān)鍵字是有聲明無實(shí)現(xiàn),并不是真的把這個(gè)函數(shù)連帶聲明刪除了。所以StackOnly()一看有我們自己寫的拷貝構(gòu)造的聲明就會(huì)去匹配這個(gè)拷貝構(gòu)造,我們自己寫的拷貝構(gòu)造又沒有實(shí)現(xiàn)拷貝功能就會(huì)報(bào)錯(cuò)。
理解這個(gè)和編譯鏈接知識(shí)有關(guān),編譯器看到聲明就去匹配了,而不是一定要看到函數(shù)實(shí)現(xiàn)才匹配,鏈接時(shí)候才會(huì)去找實(shí)現(xiàn),當(dāng)發(fā)現(xiàn)有聲明無實(shí)現(xiàn)時(shí)就很容易導(dǎo)致鏈接錯(cuò)誤。
一個(gè)類不能被繼承
父類構(gòu)造函數(shù)私有即可,構(gòu)造時(shí)先構(gòu)造父類再構(gòu)造子類,父類構(gòu)造不出不能繼承。
最后
關(guān)于單例模式,可以說是只能創(chuàng)建一個(gè)對(duì)象的實(shí)現(xiàn)。
還有個(gè)小問題,為什么不用全局變量代替單例模式,全局定義一個(gè)唯一的變量不就行了,合理吧[doge],理論上可以,但是非常不建議,可以自行查找資料"為什么不建議使用全局變量"。
全局變量的使用可能帶來很多問題,而且很容易造成鏈接、重定義等錯(cuò)誤,如果多人協(xié)作時(shí)有人再給這個(gè)變量起個(gè)別名,那維護(hù)代碼時(shí)代價(jià)就太大了,這還只是全局變量的一個(gè)小缺點(diǎn)。
??痛a規(guī)范評(píng)分里也不建議用全局變量,當(dāng)然寫題時(shí)代碼沒那么長(zhǎng),幾個(gè)全局變量倒時(shí)不打緊。
.h不能包含定義,不然多個(gè)cpp去包含就會(huì)有鏈接錯(cuò)誤,盡量把定義和聲明分離開來
【82】【Cherno C++】【中字】C++的單例模式_嗶哩嗶哩_bilibili
到此這篇關(guān)于C++深入詳解單例模式與特殊類設(shè)計(jì)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)C++單例模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Qt中圖片旋轉(zhuǎn)縮放操作的實(shí)現(xiàn)
本文主要介紹了Qt中圖片旋轉(zhuǎn)縮放操作的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01C語(yǔ)言中計(jì)算字符串長(zhǎng)度與分割字符串的方法
這篇文章主要介紹了C語(yǔ)言中計(jì)算字符串長(zhǎng)度與分割字符串的方法,是C語(yǔ)言入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-08-08C++ 11實(shí)現(xiàn)檢查是否存在特定的成員函數(shù)
C++11/14相比以往的C++98/03在很多方面做了簡(jiǎn)化和增強(qiáng),尤其是在泛型編程方面,讓C++的泛型編程的威力變得更加強(qiáng)大,下面這篇文章主要介紹了利用C++ 11實(shí)現(xiàn)檢查是否存在特定成員函數(shù)的相關(guān)資料,需要的朋友可以參考下。2017-02-02Visual Studio 2019 Professional 激活方法詳解
這篇文章主要介紹了Visual Studio 2019 Professional 激活方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05C++判斷一個(gè)點(diǎn)是否在圓內(nèi)的方法
這篇文章主要為大家詳細(xì)介紹了C++判斷一個(gè)點(diǎn)是否在圓內(nèi)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05Visual Studio Code運(yùn)行程序時(shí)輸出中文成亂碼問題及解決方法
這篇文章主要介紹了解決Visual Studio Code運(yùn)行程序時(shí)輸出中文成亂碼問題,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03