今天讓我們來(lái)分析一下C++中的智能指針和異常,首先呢先普及一下概念!
創(chuàng)新互聯(lián)企業(yè)建站,十余年網(wǎng)站建設(shè)經(jīng)驗(yàn),專注于網(wǎng)站建設(shè)技術(shù),精于網(wǎng)頁(yè)設(shè)計(jì),有多年建站和網(wǎng)站代運(yùn)營(yíng)經(jīng)驗(yàn),設(shè)計(jì)師為客戶打造網(wǎng)絡(luò)企業(yè)風(fēng)格,提供周到的建站售前咨詢和貼心的售后服務(wù)。對(duì)于網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)中不同領(lǐng)域進(jìn)行深入了解和探索,創(chuàng)新互聯(lián)在網(wǎng)站建設(shè)中充分了解客戶行業(yè)的需求,以靈動(dòng)的思維在網(wǎng)頁(yè)中充分展現(xiàn),通過(guò)對(duì)客戶行業(yè)精準(zhǔn)市場(chǎng)調(diào)研,為客戶提供的解決方案。
(1)智能指針:智能或者自動(dòng)化的管理指針?biāo)鶗?huì)向的動(dòng)態(tài)資源的釋放。
(2)異常:當(dāng)一個(gè)函數(shù)發(fā)現(xiàn)自己無(wú)法處理的錯(cuò)誤時(shí),讓函數(shù)的調(diào)用者直接或間接的處理這個(gè)問(wèn)題。
(3)RAII:資源分配即初始化。構(gòu)造函數(shù)完成對(duì)象的初始化,析構(gòu)函數(shù)完成對(duì)象的清理,而不是刪除。
在實(shí)際寫(xiě)代碼過(guò)程中,我們很容易寫(xiě)出存在異常的代碼,不信來(lái)看看下面幾個(gè)例子 :
void Test() { int *p = new int(1); if (1) { return; } delete p; }
很容易可以看出在if語(yǔ)句中已經(jīng)返回了,那后面的代碼自然是執(zhí)行不了了,所以就出現(xiàn)了內(nèi)存泄露的危險(xiǎn),這可是非??膳碌哪?,它可能會(huì)耗盡內(nèi)存,不僅當(dāng)前程序會(huì)崩潰,嚴(yán)重的整個(gè)系統(tǒng)都會(huì)崩潰,這是看你怎么辦,哈哈。這時(shí)肯定會(huì)有人想到了C++里面不是有異常捕獲嗎?是的,為了增加代碼的兼容性,C++采用了下面的代碼來(lái)捕獲異常:
throw 拋出異常; try { //可能發(fā)生異常的代碼 } catch (異常類型) { //發(fā)生異常后的處理方法 }
上面的代碼進(jìn)行這樣處理不就沒(méi)事了嗎?
void Test() { int *p = new int(1); try { if (1) { throw 1; } } catch (int e) { delete p; throw; } delete p; }
但是這里在catch中卻二次拋出異常,這樣管理起來(lái)非常混亂。所以就引入了智能指針,用它來(lái)解決異常更方便。上面提到的RAII就是編寫(xiě)異常安全代碼的關(guān)鍵思想。
下來(lái)介紹一下Boost庫(kù)里的智能指針吧。
下面給出這些智能指針的模擬實(shí)現(xiàn)接口,具體的實(shí)現(xiàn)自己完成嘍
(1)AutoPtr有兩種實(shí)現(xiàn)方法
//現(xiàn)代寫(xiě)法 templateclass AutoPtr { public: AutoPtr(T* ptr); AutoPtr(AutoPtr & ap); AutoPtr & operator=(AutoPtr & ap); T& operator*(); T* operator->(); ~AutoPtr(); protected: T* _ptr; }; //舊方法 template class AutoPtr { public: AutoPtr(T* ptr); AutoPtr(AutoPtr & ap); AutoPtr & operator=(AutoPtr & ap); T& operator*(); T* operator->(); ~AutoPtr(); protected: T* _ptr; bool _owner; };
很容易可以看出舊寫(xiě)法里多了一個(gè)布爾類型的成員變量,大家都知道AutoPtr采用的是管理權(quán)轉(zhuǎn)移的方式來(lái)管理的。所以誰(shuí)是資源的管理者,誰(shuí)的_owner值就為true,其他的對(duì)象的_owner自然就是false。在現(xiàn)代寫(xiě)法中去除了這種方式,直接采用對(duì)象賦空的方式,看起來(lái)還是舊的寫(xiě)法似乎更為合理一點(diǎn),但是新事物的出現(xiàn)肯定有它存在的理由,下來(lái)我們看個(gè)例子:
AutoPtrap1(new int(1)); if (某個(gè)條件) { AutoPtr ap2(ap1); }
大家想到了嗎?對(duì)于舊寫(xiě)法,如果進(jìn)入if語(yǔ)句,創(chuàng)建ap2后進(jìn)行拷貝ap1._owner=false,ap2._owner=true;但是出了if代碼塊,就出了ap2的作用域,ap2被釋放,但ap1里還保存著原來(lái)的地址,所以可能發(fā)生二次釋放或訪問(wèn)問(wèn)題,但這個(gè)時(shí)候ap1就是野指針啦,后果很嚴(yán)重呢!
但對(duì)于現(xiàn)代寫(xiě)法,拷貝后將ap1賦空,想要再用ap1時(shí)進(jìn)行檢測(cè)為空,那也就避免使用它啦。
(2)ScopedPtr
templateclass ScopedPtr { public: ScopedPtr(T* ptr); ~ScopedPtr(); T& operator*(); T* operator->(); protected: ScopedPtr(ScopedPtr & sp); ScopedPtr & operator=(ScopedPtr & sp); protected: T* _ptr; };
容易看出它里面直接把拷貝構(gòu)造函數(shù)和重載賦值函數(shù)聲明為了保護(hù)或者私有的,在類外是不能被調(diào)用的,也就起到了防拷貝的作用。
(3)SharedPtr
templateclass SharedPtr { public: SharedPtr(T* ptr); SharedPtr(const SharedPtr & sp); ~SharedPtr(); SharedPtr & operator=(const SharedPtr & sp); T& operator*(); T* operator->(); public: void UseCount(); void GetPtr(); protected: void _Release(); protected: T* _ptr; long* _pCount; };
(4)ScopedArray/SharedArray
templateclass ScopedArray { public: ScopedArray(T* ptr); ~ScopedArray(); T& operator[](size_t index); protected: ScopedArray(const ScopedArray & sp); ScopedArray & operator=(const ScopedArray & sp); protected: T* _ptr; }; // template class SharedArray { public: SharedArray(T* ptr); ~SharedArray(); SharedArray(const SharedArray & sp); SharedArray & operator=(const SharedArray & sp); T& operator[](size_t index); protected: void _Release(); protected: T* _ptr; long* _pCount; };
這些當(dāng)中SharedPtr是使用最廣泛的,但在上面SharedPtr的實(shí)現(xiàn)方法中還存在幾個(gè)重要的問(wèn)題:
1,引用計(jì)數(shù)器更新存在著線程安全
對(duì)于這個(gè)問(wèn)題呢,我還沒(méi)有能力去解決,待我學(xué)成歸來(lái),一定收拾它。
2,循環(huán)引用
我們用一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明一下什么是循環(huán)引用
#includestruct Node//定義一個(gè)雙向鏈表 { shared_ptr _next; shared_ptr _prev; ~Node() { cout << "delete" << this << endl; } }; void TestSharedPtr() { shared_ptr cur(new Node()); shared_ptr next(new Node()) cur->_next = next; next->_prev = cur; }
運(yùn)行這段代碼你會(huì)發(fā)現(xiàn),咦,為什么沒(méi)有輸出呢?也就是說(shuō)cur和next根本沒(méi)用被釋放。大家看下面的圖
在用SharedPtr實(shí)現(xiàn)雙向鏈表時(shí),會(huì)發(fā)生循環(huán)引用,這是一個(gè)很特殊的場(chǎng)景。
因?yàn)閏ur->_next和next->_prev相互依賴,所以在出了作用域以后都等著對(duì)方釋放,處于一種僵持狀態(tài)。因而沒(méi)用調(diào)用析構(gòu)函數(shù)。
解決方法是采用weak_ptr弱指針,如下:
#includestruct Node//定義一個(gè)雙向鏈表 { weak_ptr _next; weak_ptr _prev; ~Node() { cout << "delete" << this << endl; } };
weak_ptr在創(chuàng)建對(duì)象時(shí)引用計(jì)數(shù)為1,而在cur->_next=next和next->_prev=cur中對(duì)cur和next的引用計(jì)數(shù)不加1,這就很好的解決了循環(huán)引用的問(wèn)題。
3,定置刪除器
先介紹一個(gè)概念仿函數(shù),顧名思義它類似于函數(shù)但并不是函數(shù),而是一個(gè)類。只不過(guò)在類的對(duì)象調(diào)用成員函數(shù)的時(shí)候特別像是函數(shù)的調(diào)用,也就是在 這個(gè)類中重載了operator()。看兩個(gè)小例子:
(1)
void TestSharedPtr() { int *p1 = (int *)malloc(sizeof(int)* 10); shared_ptrsp1(p1); }
程序看上去沒(méi)有問(wèn)題,可能也沒(méi)有崩潰,可是malloc和free要成對(duì)使用,這里存在內(nèi)存泄露的問(wèn)題。
(2)
void TestSharedPtr() { FILE* p2 = fopen("test.cpp", "r"); shared_ptrsp2(p2,Fclose());//出作用域釋放文件,發(fā)生崩潰 }
當(dāng)shared_ptr對(duì)象是文件時(shí),出作用域釋放,系統(tǒng)自然會(huì)崩潰。
怎么解決上面的問(wèn)題的?
struct Free { void operator()(void* ptr) { cout << "Free:" << ptr << endl; free(ptr); } }; struct Fclose { void operator()(void* ptr) { cout << "Fclose" << ptr << endl; fclose((FILE*)ptr); } }; void TestSharedPtr() { int *p1 = (int *)malloc(sizeof(int)* 10); shared_ptrsp1(p1,Free()); Fclose f; FILE* p2 = fopen("test.cpp", "r"); shared_ptr sp2(p2,Fclose()); }
這就是一個(gè)仿函數(shù)的使用實(shí)例。
那接下來(lái)就用它來(lái)模擬實(shí)現(xiàn)shared_ptr的定置刪除器吧,同樣還是給出接口。
templatestruct DefaultDel { void operator()(T* ptr) { delete ptr; } }; template struct Free { void operator()(T* ptr) { free(ptr); } }; template > class SharedPtr { public: SharedPtr(T* ptr); SharedPtr(T* ptr,D del); SharedPtr(const SharedPtr & sp); ~SharedPtr(); SharedPtr & operator=(const SharedPtr & sp); T& operator*(); T* operator->(); public: void UseCount(); void GetPtr(); protected: void _Release(); protected: T* _ptr; long* _pCount; D _del; }; void TestDeleter() { SharedPtr > sp1(new int(1)); SharedPtr > sp2((int *)malloc(sizeof(int))); SharedPtr sp3(new int(1)); SharedPtr > sp4((int *)malloc(sizeof(int))); }
好了,智能指針就說(shuō)這么多啦。小伙伴明白了嗎?