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