C++單例設(shè)計(jì)模式詳細(xì)講解
特殊類設(shè)計(jì)
只能在堆上創(chuàng)建對(duì)象的類
請(qǐng)?jiān)O(shè)計(jì)一個(gè)類,只能在堆上創(chuàng)建對(duì)象
實(shí)現(xiàn)方式:
- 將類的構(gòu)造函數(shù)私有,拷貝構(gòu)造聲明成私有。防止別人調(diào)用拷貝在棧上生成對(duì)象。
- 提供一個(gè)靜態(tài)的成員函數(shù),在該靜態(tài)成員函數(shù)中完成堆對(duì)象的創(chuàng)建
class test { public: static test* GetObj() { return new test(); //堆上申請(qǐng)并創(chuàng)建一個(gè)對(duì)象 } private: //構(gòu)造函數(shù)私有 test() { cout << "調(diào)用了構(gòu)造函數(shù)" << endl; } //拷貝構(gòu)造私有化,無法實(shí)例化對(duì)象 test(const test& obj){}; }; void func1() { test* p = test::GetObj(); //通過調(diào)用靜態(tài)函數(shù)獲取對(duì)象的指針 }
請(qǐng)?jiān)O(shè)計(jì)一個(gè)類只能在棧上創(chuàng)建對(duì)象
方法一:同上將構(gòu)造函數(shù)私有化,然后設(shè)計(jì)靜態(tài)方法創(chuàng)建對(duì)象返回即可。
//只能在棧上創(chuàng)建對(duì)象 class test1 { public: static test1 GetObj() { return test1(); //棧上創(chuàng)建一個(gè)對(duì)象并返回 } test1() { cout << "調(diào)用了構(gòu)造函數(shù)" << endl; } private: //拷貝構(gòu)造私有化無法實(shí)例化對(duì)象 test1(const test1& obj) { } };
方法二:屏蔽new,因?yàn)閚ew在底層調(diào)用void* operator new(size_t size)函數(shù)(會(huì)在堆上開辟空間),我們只需要在類里面自定義定位new和delete就會(huì)不再new的時(shí)候調(diào)用全局的operator new和operator delete 最后該函數(shù)私有化,也就防止了在堆上創(chuàng)建對(duì)象,注意:也要防止定位new
class test02 { public: test02() { } private: //自定義的定位new、定位delete void *operator new(size_t size) { //代碼.... } void operator delete(void *p) { //代碼.... } };
請(qǐng)?jiān)O(shè)計(jì)一個(gè)類不能被拷貝
拷貝只會(huì)放生在兩個(gè)場(chǎng)景中:拷貝構(gòu)造函數(shù)以及賦值運(yùn)算符重載,因此想要讓一個(gè)類禁止拷貝,只需讓該類不能調(diào)用拷貝構(gòu)造函數(shù)以及賦值運(yùn)算符重載即可。
C++98將拷貝構(gòu)造函數(shù)與賦值運(yùn)算符重載只聲明不定義,并且將其訪問權(quán)限設(shè)置為私有即可
原因:
- 設(shè)置成私有:如果只聲明沒有設(shè)置成private,用戶自己如果在類外定義了,就可以不能禁止拷貝了
- 只聲明不定義:不定義是因?yàn)樵摵瘮?shù)根本不會(huì)調(diào)用,定義了其實(shí)也沒有什么意義,不寫反而還簡(jiǎn)單,而且如果定義了就不會(huì)防止成員函數(shù)內(nèi)部拷貝了。
- C++11的寫法也可以在 默認(rèn)成員函數(shù)后面加上delete,表示讓編譯器刪除掉該默認(rèn)成員函數(shù)
class CopyBan { // ... private: CopyBan(const CopyBan&); CopyBan& operator=(const CopyBan&); //... };
請(qǐng)?jiān)O(shè)計(jì)一個(gè)類不能被繼承
C++98會(huì)對(duì)父類的構(gòu)造函數(shù)私有化,但是這個(gè)方法并不夠徹底,實(shí)際上是子類還是繼承了父類的,只是沒辦法實(shí)例化對(duì)象,那么也就沒有意義了
class test { private: test(){} }
C++11方法
final關(guān)鍵字,final修飾類,表示該類不能被繼承
class A final { // .... };
請(qǐng)?jiān)O(shè)計(jì)一個(gè)類只能創(chuàng)建一個(gè)對(duì)象(單例模式)
設(shè)計(jì)模式(Design Pattern)是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。為什么會(huì)產(chǎn)生設(shè)計(jì)模式這樣的東西呢?就像人類歷史發(fā)展會(huì)產(chǎn)生兵法。最開始部落之間打仗時(shí)都是人拼人的對(duì)砍。后來春秋戰(zhàn)國(guó)時(shí)期,七國(guó)之間經(jīng)常打仗,就發(fā)現(xiàn)打仗也是有套路的,后來孫子就總結(jié)出了《孫子兵法》。孫子兵法也是類似。
使用設(shè)計(jì)模式的目的:為了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。 設(shè)計(jì)模式使代碼編寫真正工程化;設(shè)計(jì)模式是軟件工程的基石脈絡(luò),如同大廈的結(jié)構(gòu)一樣。
單例模式:
一個(gè)類只能創(chuàng)建一個(gè)對(duì)象,即單例模式,該模式可以保證系統(tǒng)中該類只有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn),該實(shí)例被所有程序模塊共享。比如在某個(gè)服務(wù)器程序中,該服務(wù)器的配置信息存放在一個(gè)文件中,這些配置數(shù)據(jù)由一個(gè)單例對(duì)象統(tǒng)一讀取,然后服務(wù)進(jìn)程中的其他對(duì)象再通過這個(gè)單例對(duì)象獲取這些配置信息,這種方式簡(jiǎn)化了在復(fù)雜環(huán)境下的配置管理。
單例模式有兩種實(shí)現(xiàn)模式:
餓漢模式、就是說不管你將來用不用,程序啟動(dòng)時(shí)就創(chuàng)建一個(gè)唯一的實(shí)例對(duì)象,如果這個(gè)單例對(duì)象在多線程高并發(fā)環(huán)境下頻繁使用,性能要求較高,那么顯然使用餓漢模式來避免資源競(jìng)爭(zhēng),提高響應(yīng)速度更好。
實(shí)現(xiàn)方法:在類里面定義一個(gè)靜態(tài)的成員指針obj,通過提供一個(gè)靜態(tài)的成員函數(shù)獲取該指針obj
class Singleton { public: static Singleton* GetInstance() { return obj; } private: Singleton() {} Singleton(const Singleton& obj) {} static Singleton* obj; }; Singleton* Singleton::obj = new Singlet on;
懶漢模式、如果在套用單例設(shè)計(jì)模式,單例對(duì)象構(gòu)造十分耗時(shí)或者占用很多資源,比如加載插件啊, 初始化網(wǎng)絡(luò)連接啊,讀取文件啊等等,而有可能該對(duì)象程序運(yùn)行時(shí)不會(huì)用到,那么也要在程序一開始就進(jìn)行初始化,就會(huì)導(dǎo)致程序啟動(dòng)時(shí)非常的緩慢。 所以這種情況使用懶漢模式(延遲加載)更好。
懶漢模式:我們需要考慮到多線程的安全隱患,和處理的返回值指針指向的永遠(yuǎn)都是同一個(gè)對(duì)象的指針,這樣子才不會(huì)失去單例對(duì)象的性質(zhì),主要改變的是static Singleton* GetInstance();接口函數(shù)的設(shè)計(jì)
static Singleton* GetInstance() { //如果只有一層的if判斷的我們的程序就會(huì)存在一定的問題, //在多線程的場(chǎng)景下,多個(gè)線程都是比較自由的,可讀可寫、或者只讀、只寫 //這樣在一些場(chǎng)景下就會(huì)導(dǎo)致單例對(duì)象失去他本身的性質(zhì), //比如:兩個(gè)線程 th1、th2,同時(shí)進(jìn)入到了if語句中,假設(shè)th1會(huì)先執(zhí)行。 //為obj 申請(qǐng)了一個(gè)對(duì)象并返回它的指針,難么這個(gè)實(shí)例對(duì)象也就創(chuàng)建了 //最后return obj,th1結(jié)束后(不確定結(jié)束時(shí)間), //th2又會(huì)再次new一個(gè)對(duì)象給obj,最后導(dǎo)致obj被賦值兩次 //整個(gè)過程梳理: //1、th1線程存在內(nèi)存泄漏,因?yàn)閛bj被賦值兩次,而第一次new出來的對(duì)象并沒有被釋放,那么就存在了內(nèi)存泄漏 //2、obj被賦值兩次,失去單例對(duì)象性質(zhì) if (!obj) { if (!obj) { obj = new Singleton(); } } return obj; //返回對(duì)象的指針 }
改善一:引入C++11線程庫,加入互斥鎖管理線程
static Singleton* GetInstance() { //通過加鎖,保證了線程安全 m_mtx.lock(); //加鎖 //假設(shè)th1先拿到鎖,那么th1就會(huì)先執(zhí)行下面的語句,而th2就會(huì)等待, //obj就會(huì)指向new出來的單例對(duì)象,最后th1解鎖完了return ,而th2 //才剛拿到鎖繼續(xù)if判斷的時(shí)候obj已經(jīng)有值了就會(huì)跳過if繼續(xù)往下執(zhí) //行,然后解鎖最終return ,整個(gè)過程中單例對(duì)象只有一份, //不存在二次賦值 if (!obj) { obj = new Singleton(); } m_mtx.unlock(); //解鎖 return obj; //返回對(duì)象的指針 }
進(jìn)一步優(yōu)化
因?yàn)榈谝淮渭渔i解鎖之后,處理了線程安全的問題,而往后的obj指針已經(jīng)被初始化了,也就不需要再new一次,所以可以再最外層套上一層if判斷,防止繼續(xù)枷鎖解鎖,因?yàn)轭l繁的加鎖解鎖會(huì)導(dǎo)致線程不斷的切入切出有上下文切換的開銷
static Singleton* GetInstance() { if(!obj) //第一次判斷保護(hù)線程安全,第二次obj已經(jīng)有值,不需要執(zhí)行{....} //優(yōu)點(diǎn):保證線程安全的同時(shí)完成了單例對(duì)象的初始化 { m_mtx.lock(); //加鎖 if (!obj) //保證單例對(duì)象只有一份,不會(huì)存在二次賦值 { obj = new Singleton(); } m_mtx.unlock(); //解鎖 } return obj; //返回對(duì)象的指針 }
完善實(shí)現(xiàn)
//懶漢 class Singleton { public: class CGarbo { public: ~CGarbo() { if (obj) //釋放對(duì)象指針,并置空 { delete obj; obj = nullptr; } } }; public: static Singleton* GetInstance() { //雙重檢查的好處是保護(hù)了線程安全,同時(shí)又提高了效率 if (!obj) { m_mtx.lock(); //加鎖 if (!obj) { obj = new Singleton(); } m_mtx.unlock(); //解鎖 } return obj; //返回對(duì)象的指針 } private: Singleton() {} Singleton(const Singleton& obj) {} void operator=(const Singleton& obj) {} static Singleton* obj; //聲明對(duì)象指針 static mutex m_mtx; //聲明互斥鎖 static CGarbo Garbo; //聲明垃圾回收器對(duì)象 }; Singleton* Singleton::obj = nullptr; //對(duì)象指針初始化 mutex Singleton::m_mtx; //定義互斥鎖 Singleton::CGarbo Garbo; //定義垃圾回收器對(duì)象
懶漢模式和餓漢模式的對(duì)比
餓漢
優(yōu)點(diǎn):簡(jiǎn)單
缺點(diǎn):1、如果單例對(duì)象構(gòu)造函數(shù)工作比較多,會(huì)導(dǎo)致程序啟動(dòng)慢,遲遲進(jìn)不了入口main函數(shù)
2、如果有多個(gè)單例對(duì)象,他們之間有初始化依賴關(guān)系,餓漢模式也會(huì)有問題。
比如有A和B兩個(gè)單例類,要求A單例先初始化,B必須在A之后初始化。那么餓漢無法保證
這種場(chǎng)景下面用懶漢就可以,懶漢可以先調(diào)用A::GetInstance(),再調(diào)用B::GetInstance().
懶漢
優(yōu)點(diǎn):解決上面餓漢的缺點(diǎn)。因?yàn)樗堑谝淮握{(diào)用GetInstance時(shí)創(chuàng)建初始化單例對(duì)象
缺點(diǎn):相對(duì)餓漢,復(fù)雜一點(diǎn)點(diǎn)。
迭代器模式、適配器模式
設(shè)計(jì)模式有興趣的同學(xué),可以下去再看看工廠模式、觀察者模式等等
到此這篇關(guān)于C++單例設(shè)計(jì)模式詳細(xì)講解的文章就介紹到這了,更多相關(guān)C++單例設(shè)計(jì)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言中求和、計(jì)算平均值、方差和標(biāo)準(zhǔn)差的實(shí)例
這篇文章主要介紹了C語言中求和、計(jì)算平均值、方差和標(biāo)準(zhǔn)差的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12C++實(shí)現(xiàn)公司人事管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)公司人事管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03用c語言根據(jù)可變參數(shù)合成字符串的實(shí)現(xiàn)代碼
本篇文章是對(duì)用c語言根據(jù)可變參數(shù)合成字符串的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05Cocos2d-x中使用CCScrollView來實(shí)現(xiàn)關(guān)卡選擇實(shí)例
這篇文章主要介紹了Cocos2d-x中使用CCScrollView來實(shí)現(xiàn)關(guān)卡的選擇實(shí)例,本文在代碼中用大量注釋講解了CCScrollView的使用,需要的朋友可以參考下2014-09-09