詳解C++中特殊類設(shè)計(jì)
設(shè)計(jì)一個(gè)不能拷貝的類
類之間的拷貝主要是通過拷貝構(gòu)造和賦值運(yùn)算符之間來實(shí)現(xiàn)的;
為此,我們只要讓外部用戶無法正常調(diào)用這兩個(gè)函數(shù)就行了;
C++98作法
將拷貝構(gòu)造、賦值運(yùn)算符的聲明進(jìn)行私有化處理;(不建議設(shè)為protected,不然的話在子類中就能進(jìn)行拷貝了)
這樣在外部就會(huì)因?yàn)樵L問限定符的限制不能進(jìn)行拷貝:
class NoCopy { public: NoCopy(int val=0):_val(val) {} private: NoCopy(const NoCopy&); NoCopy& operator=(NoCopy&); int _val; };
C++11作法
C++只用delete關(guān)鍵字可以不讓默認(rèn)拷貝構(gòu)造和默認(rèn)賦值運(yùn)算符重載默認(rèn)生成,我們只要自己不去手寫拷貝構(gòu)造和賦值運(yùn)算符重載就可以了:
class NoCopy { public: NoCopy(const NoCopy&) = delete; NoCopy& operator=(NoCopy&) = delete; NoCopy(int val=0):_val(val) {} private: int _val; };
設(shè)計(jì)一個(gè)類只允許在堆上創(chuàng)建對象
思路1:
1.封構(gòu)造:防止在類外隨意創(chuàng)建對象
2.設(shè)計(jì)一個(gè)成員函數(shù)接口來創(chuàng)建對象,讓用戶只能通過這個(gè)函數(shù)接口來創(chuàng)建對象,并且創(chuàng)建出來的對象一定是堆上的;(該函數(shù)不一定非要是靜態(tài)的,非靜態(tài)的函數(shù)也是可以完成用于創(chuàng)建對象的);
3.封拷貝:防止在在類外利用特定函數(shù)接口創(chuàng)建出來的對象在其它地方利用拷貝構(gòu)造實(shí)例化對象;(移動(dòng)構(gòu)造也可以考慮封一下)
class HeapOnly { public: //利用靜態(tài)成員函數(shù)來創(chuàng)建對象 //調(diào)用方法:HeapOnly::GetHeapOnly1(); static HeapOnly& GetHeapOnly1(int val=0) { return *(new HeapOnly(val)); } //利用非靜態(tài)成員函數(shù)創(chuàng)建對象 //調(diào)用方法: // HeapOnly*p=nullptrl; //p->GetHeapOnly2(); HeapOnly& GetHeapOnly2(int val=0) { return *(new HeapOnly(val)); } private: //封拷貝:放在在類外利用GetHeapOnly()創(chuàng)建出來的對象在其它地方拷貝構(gòu)造 //移動(dòng)構(gòu)造也可以考慮封一下 HeapOnly(HeapOnly&&) = delete; HeapOnly(const HeapOnly&) = delete; HeapOnly&operator=(const HeapOnly&) = delete; //封構(gòu)造:防止在類外隨意創(chuàng)建對象 HeapOnly(int val = 0) :_val(val) {} int _val; };
思路2:
1.封析構(gòu):那么就無法在靜態(tài)區(qū)、棧區(qū)創(chuàng)建對象,因?yàn)檫@些地方在對象生命周期到了過后編譯器無法在外部調(diào)用析構(gòu),自然的編譯器就不允許在這些地方創(chuàng)建對象;
2.那么自然的我們最后在堆區(qū)上創(chuàng)建的對象也無法使用delete來釋放資源,但是為了避免內(nèi)存泄漏,我們可以在類內(nèi)封裝一個(gè)destory函數(shù)來進(jìn)行手動(dòng)調(diào)用釋放資源;
class HeapOnly { public: void destroy() { delete this; } HeapOnly(int val = 0) :_val(val) {} private: ~HeapOnly() {} int _val; };
設(shè)計(jì)一個(gè)類只允許在棧上創(chuàng)建對象
思路:
1.封構(gòu)造:防止在類外任意構(gòu)造
2.設(shè)計(jì)一個(gè)在棧上創(chuàng)建對象的接口,讓用戶只能通過該函數(shù)獲取對象
3.注意不要封拷貝!之所以能夠在棧上創(chuàng)建對象就多虧了拷貝構(gòu)造?。ɡ媒涌讷@取到的對象拷貝構(gòu)造類外棧上的對象)
class StackOnly { public: static StackOnly GetStackOnly1(int val = 0) { return StackOnly(val); } StackOnly GetStackOnly2(int val = 0) { return StackOnly(val); } private: StackOnly(int val=0) :_val(val) {} int _val; };
設(shè)計(jì)一個(gè)類不能被繼承
思路1:
1、封構(gòu)造:也就是將構(gòu)造私有化,那么子類就無法訪問父類的私有成員,子類在初始化的時(shí)候就無法調(diào)用父類的構(gòu)造函數(shù)來初始化,就無法完成對于父類的繼承!
class A { private: A(int a = 0) :_a(a) {} int _a; }; class B :public A { };
思路2:
利用final關(guān)鍵字修飾類,被final關(guān)鍵字修飾的類無法被繼承;
class A final { public: A(int a = 0) :_a(a) {} int _a; }; class B :public A {};
設(shè)計(jì)一個(gè)類只能實(shí)例化一次對象(單例模式)
單例模式簡介:一個(gè)類只能創(chuàng)建一個(gè)對象,即單例模式,該模式可以保證系統(tǒng)中該類只有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn),該實(shí)例被所有程序模塊共享。比如在某個(gè)服務(wù)器程序中,該服務(wù)器的配置信息存放在一個(gè)文件中,這些配置數(shù)據(jù)由一個(gè)單例對象統(tǒng)一讀取,然后服務(wù)進(jìn)程中的其他對象再通過這個(gè)單例對象獲取這些配置信息,這種方式簡化了在復(fù)雜環(huán)境下的配置管理.
單例模式有兩種實(shí)現(xiàn)模式
1. 餓漢模式
還沒啟動(dòng)main函數(shù)之前就已經(jīng)將對象創(chuàng)建好了;
2. 懶漢模式
程序啟動(dòng)的時(shí)候,第一次獲取對象時(shí)進(jìn)行創(chuàng)建;
餓漢模式
思路:
1.封構(gòu)造:避免在類外隨意創(chuàng)建對象;
2.提供一個(gè)函數(shù)接口(GetInstance())來獲取這個(gè)唯一對象;
3.用一個(gè)封裝一個(gè)本類的靜態(tài)指針,一開始就初始化這個(gè)指針,該指針指向一個(gè)堆區(qū)的對象;
class Hungry { public: static Hungry&GetInstance() { return *_ptr; } private: Hungry(int x=int (),const Date&d=Date()):_x(x),_d(d) {} int _x; Date _d;//自定義類型 static Hungry* _ptr; }; Hungry* Hungry::_ptr = new Hungry(1,Date(2023,12,31));
餓漢模式總結(jié):
1.使用餓漢模式會(huì)加慢程序響應(yīng)的時(shí)間,因?yàn)轲I漢模式是一上來就創(chuàng)建對象,要是這個(gè)對象非常大呢?那么創(chuàng)建起來就比較耗時(shí)間,這樣會(huì)拖慢main函數(shù)的調(diào)用時(shí)機(jī)!
2.我們無法保證我們程序一運(yùn)行起來就會(huì)立馬使用餓漢模式創(chuàng)建出來的對象,這就會(huì)造成餓漢模式下的對象白白占用著空間,卻無法得到有效利用,這是一種浪費(fèi)!
3.如果這個(gè)單例對象在多線程高并發(fā)環(huán)境下頻繁使用,性能要求較高,那么顯然使用餓漢模式來避免資源競爭,提高響應(yīng)速度更好。
懶漢模式
思路:
1.封構(gòu)造:避免在類外隨意進(jìn)行實(shí)例化對象;
2.提供一個(gè)接口(GetInstance())來專門獲取這個(gè)單例對象,同時(shí)這個(gè)函數(shù)接口具有判斷是不是第一次獲取單例對象的能力;
3.封裝一個(gè)static本類指針用于指向單例對象(初始化時(shí)為nullptr);
以上就是懶漢模式的雛形,可是上面的懶漢模型真的沒有問題嗎?
當(dāng)然有,在單線程下沒問題,可是在多線程下,要是多個(gè)線程第一次并發(fā)訪問GetInstance()呢?
會(huì)不會(huì)造成多份sluggard對象被開辟呢?但是_ptr只能存一個(gè)sluggard對象的地址,也就意味著那么沒有被_ptr記錄下來的多余sluggard對象會(huì)造成嚴(yán)重的內(nèi)存泄漏問題,為此我們需要給_ptr指針配一把鎖來保護(hù)它的線程安全;
以上就是我們的第一個(gè)懶漢模型,上面的懶漢模式還有問題嗎?
還有的!就是當(dāng)我們每一次都用GetInstance()獲取對象的時(shí)候都需要申請鎖和釋放鎖,有一點(diǎn)影響效率,實(shí)際上這把鎖只需要第一次訪問時(shí)使用就夠了,后面的訪問我們就不需要再進(jìn)行申請鎖、釋放鎖了,為此我們可以按照如下的方式改:
可是上面的懶漢還是有一點(diǎn)小問題,就是new的時(shí)候是會(huì)失敗的,new失敗是會(huì)拋出異常的,此時(shí)執(zhí)行流會(huì)直接跳到捕獲該異常的地方,這樣就會(huì)造成死鎖問題,因?yàn)閳?zhí)行流在跳之前是已經(jīng)申請了鎖的,然后是以沒解鎖的狀態(tài)走的,這就必然造成了死鎖問題!為此這把鎖我們需要交給具有RAII風(fēng)格的智能鎖來管理:
class sluggard { public: static sluggard& GetInstance() { if (!_ptr) { lock_guard<mutex> lock(_mux); if (!_ptr) _ptr = new sluggard; } return *_ptr; } private: sluggard(int c = int(), const Date& d=Date()) :_x(c), _d(d) {} int _x; Date _d; static sluggard* _ptr; static mutex _mux; }; mutex sluggard::_mux; sluggard* sluggard::_ptr=nullptr;
懶漢模式總結(jié):
1.相比于餓漢模式,懶漢模式不會(huì)拖慢程序的啟動(dòng)速度,同時(shí)是只有需要的時(shí)候才進(jìn)行創(chuàng)建,提高了空間資源的利用率;
2.缺點(diǎn)就是設(shè)計(jì)比較復(fù)雜!
以上就是詳解C++中特殊類設(shè)計(jì)的詳細(xì)內(nèi)容,更多關(guān)于C++特殊類的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語言鏈表實(shí)現(xiàn)學(xué)生成績管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語言鏈表實(shí)現(xiàn)學(xué)生成績管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07