您好,登錄后才能下訂單哦!
今天讓我們來分析一下C++中的智能指針和異常,首先呢先普及一下概念!
(1)智能指針:智能或者自動化的管理指針所會向的動態資源的釋放。
(2)異常:當一個函數發現自己無法處理的錯誤時,讓函數的調用者直接或間接的處理這個問題。
(3)RAII:資源分配即初始化。構造函數完成對象的初始化,析構函數完成對象的清理,而不是刪除。
在實際寫代碼過程中,我們很容易寫出存在異常的代碼,不信來看看下面幾個例子 :
void Test() { int *p = new int(1); if (1) { return; } delete p; }
很容易可以看出在if語句中已經返回了,那后面的代碼自然是執行不了了,所以就出現了內存泄露的危險,這可是非常可怕的呢 ,它可能會耗盡內存,不僅當前程序會崩潰,嚴重的整個系統都會崩潰,這是看你怎么辦,哈哈。這時肯定會有人想到了C++里面不是有異常捕獲嗎?是的,為了增加代碼的兼容性,C++采用了下面的代碼來捕獲異常:
throw 拋出異常; try { //可能發生異常的代碼 } catch (異常類型) { //發生異常后的處理方法 }
上面的代碼進行這樣處理不就沒事了嗎?
void Test() { int *p = new int(1); try { if (1) { throw 1; } } catch (int e) { delete p; throw; } delete p; }
但是這里在catch中卻二次拋出異常,這樣管理起來非常混亂。所以就引入了智能指針,用它來解決異常更方便。上面提到的RAII就是編寫異常安全代碼的關鍵思想。
下來介紹一下Boost庫里的智能指針吧。
下面給出這些智能指針的模擬實現接口,具體的實現自己完成嘍
(1)AutoPtr有兩種實現方法
//現代寫法 template <typename T> class AutoPtr { public: AutoPtr(T* ptr); AutoPtr(AutoPtr<T>& ap); AutoPtr<T>& operator=(AutoPtr<T>& ap); T& operator*(); T* operator->(); ~AutoPtr(); protected: T* _ptr; }; //舊方法 template<typename T> class AutoPtr { public: AutoPtr(T* ptr); AutoPtr(AutoPtr<T>& ap); AutoPtr<T>& operator=(AutoPtr<T>& ap); T& operator*(); T* operator->(); ~AutoPtr(); protected: T* _ptr; bool _owner; };
很容易可以看出舊寫法里多了一個布爾類型的成員變量,大家都知道AutoPtr采用的是管理權轉移的方式來管理的。所以誰是資源的管理者,誰的_owner值就為true,其他的對象的_owner自然就是false。在現代寫法中去除了這種方式,直接采用對象賦空的方式,看起來還是舊的寫法似乎更為合理一點,但是新事物的出現肯定有它存在的理由,下來我們看個例子:
AutoPtr<int> ap1(new int(1)); if (某個條件) { AutoPtr<int> ap2(ap1); }
大家想到了嗎?對于舊寫法,如果進入if語句,創建ap2后進行拷貝ap1._owner=false,ap2._owner=true;但是出了if代碼塊,就出了ap2的作用域,ap2被釋放,但ap1里還保存著原來的地址,所以可能發生二次釋放或訪問問題,但這個時候ap1就是野指針啦,后果很嚴重呢!
但對于現代寫法,拷貝后將ap1賦空,想要再用ap1時進行檢測為空,那也就避免使用它啦。
(2)ScopedPtr
template<typename T> class ScopedPtr { public: ScopedPtr(T* ptr); ~ScopedPtr(); T& operator*(); T* operator->(); protected: ScopedPtr(ScopedPtr<T>& sp); ScopedPtr<T>& operator=(ScopedPtr<T>& sp); protected: T* _ptr; };
容易看出它里面直接把拷貝構造函數和重載賦值函數聲明為了保護或者私有的,在類外是不能被調用的,也就起到了防拷貝的作用。
(3)SharedPtr
template<typename T> class SharedPtr { public: SharedPtr(T* ptr); SharedPtr(const SharedPtr<T>& sp); ~SharedPtr(); SharedPtr<T>& operator=(const SharedPtr<T>& sp); T& operator*(); T* operator->(); public: void UseCount(); void GetPtr(); protected: void _Release(); protected: T* _ptr; long* _pCount; };
(4)ScopedArray/SharedArray
template<typename T> class ScopedArray { public: ScopedArray(T* ptr); ~ScopedArray(); T& operator[](size_t index); protected: ScopedArray(const ScopedArray<T>& sp); ScopedArray<T>& operator=(const ScopedArray<T>& sp); protected: T* _ptr; }; // template<typename T> class SharedArray { public: SharedArray(T* ptr); ~SharedArray(); SharedArray(const SharedArray<T>& sp); SharedArray<T>& operator=(const SharedArray<T>& sp); T& operator[](size_t index); protected: void _Release(); protected: T* _ptr; long* _pCount; };
這些當中SharedPtr是使用最廣泛的,但在上面SharedPtr的實現方法中還存在幾個重要的問題:
1,引用計數器更新存在著線程安全
對于這個問題呢,我還沒有能力去解決,待我學成歸來,一定收拾它。
2,循環引用
我們用一個簡單的例子來說明一下什么是循環引用
#include<memory> struct Node//定義一個雙向鏈表 { shared_ptr<Node> _next; shared_ptr<Node> _prev; ~Node() { cout << "delete" << this << endl; } }; void TestSharedPtr() { shared_ptr<Node> cur(new Node()); shared_ptr<Node> next(new Node()) cur->_next = next; next->_prev = cur; }
運行這段代碼你會發現,咦,為什么沒有輸出呢?也就是說cur和next根本沒用被釋放。大家看下面的圖
在用SharedPtr實現雙向鏈表時,會發生循環引用,這是一個很特殊的場景。
因為cur->_next和next->_prev相互依賴,所以在出了作用域以后都等著對方釋放,處于一種僵持狀態。因而沒用調用析構函數。
解決方法是采用weak_ptr弱指針,如下:
#include<memory> struct Node//定義一個雙向鏈表 { weak_ptr<Node> _next; weak_ptr<Node> _prev; ~Node() { cout << "delete" << this << endl; } };
weak_ptr在創建對象時引用計數為1,而在cur->_next=next和next->_prev=cur中對cur和next的引用計數不加1,這就很好的解決了循環引用的問題。
3,定置刪除器
先介紹一個概念仿函數,顧名思義它類似于函數但并不是函數,而是一個類。只不過在類的對象調用成員函數的時候特別像是函數的調用,也就是在 這個類中重載了operator()。看兩個小例子:
(1)
void TestSharedPtr() { int *p1 = (int *)malloc(sizeof(int)* 10); shared_ptr<int> sp1(p1); }
程序看上去沒有問題,可能也沒有崩潰,可是malloc和free要成對使用,這里存在內存泄露的問題。
(2)
void TestSharedPtr() { FILE* p2 = fopen("test.cpp", "r"); shared_ptr<FILE> sp2(p2,Fclose());//出作用域釋放文件,發生崩潰 }
當shared_ptr對象是文件時,出作用域釋放,系統自然會崩潰。
怎么解決上面的問題的?
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_ptr<int> sp1(p1,Free()); Fclose f; FILE* p2 = fopen("test.cpp", "r"); shared_ptr<FILE> sp2(p2,Fclose()); }
這就是一個仿函數的使用實例。
那接下來就用它來模擬實現shared_ptr的定置刪除器吧,同樣還是給出接口。
template<typename T> struct DefaultDel { void operator()(T* ptr) { delete ptr; } }; template<typename T> struct Free { void operator()(T* ptr) { free(ptr); } }; template<typename T,typename D=DefaultDel<T>> class SharedPtr { public: SharedPtr(T* ptr); SharedPtr(T* ptr,D del); SharedPtr(const SharedPtr<T,D>& sp); ~SharedPtr(); SharedPtr<T,D>& operator=(const SharedPtr<T,D>& sp); T& operator*(); T* operator->(); public: void UseCount(); void GetPtr(); protected: void _Release(); protected: T* _ptr; long* _pCount; D _del; }; void TestDeleter() { SharedPtr<int, DefaultDel<int>> sp1(new int(1)); SharedPtr<int, Free<int>> sp2((int *)malloc(sizeof(int))); SharedPtr<int> sp3(new int(1)); SharedPtr<int, Free<int>> sp4((int *)malloc(sizeof(int))); }
好了,智能指針就說這么多啦。小伙伴明白了嗎?
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。