十三、智慧指標二
1、 SmartPointer
智慧指標重構
需求:使用智慧指標 SmartPointer
替換單鏈表 LinkList
中的原生指標
將原生指標更改為智慧指標後,解決全部的編譯問題,程式還是會出錯,問題在於: SmartPointer
的設計方案存在的一些特性
- 指標的生命週期結束時主動釋放堆空間
- 一片堆空間最多隻能有一個指標標識
- 不允許指標運算和指標比較
需求:建立新的指標指標
Pointer
是智慧指標的抽象父類(模板)
virtual ~Pointer() = 0 operator ->() operator* ()
智慧指標新的設計方案
template <typename T> class Pointer : public Object { protected: T* m_pointer; public: Pointer(T* p = NULL) { m_pointer = p; } T* operator-> () { return m_pointer; } T& operator* () { return *m_pointer; } bool inNull() { return (m_pointer == NULL); } T* get() { return m_pointer; } // 只要沒有實現一個具體的解構函式,Pointer繼承於Object,就是一個抽象類 };
修改 SmartPointer
,繼承於 Pointer
類
#ifndef SMARTPOINTER_H #define SMARTPOINTER_H #include "Pointer.h" namespace DTLib { // 構建一個智慧指標模板類 template<typename T> class SmartPointer : public Pointer<T> { public: // 建構函式,初始化傳參為堆空間地址 SmartPointer(T* p = NULL) : Pointer<T>(p)// 呼叫父類的建構函式的形式 { // 建構函式呼叫父類的建構函式 } // 拷貝建構函式 SmartPointer(const SmartPointer<T>& obj) { this->m_pointer = obj.m_pointer; const_cast<SmartPointer<T>&>(obj).m_pointer = NULL; // } SmartPointer<T>& operator = (const SmartPointer<T>& obj) { if(this != &obj) { // 釋放掉原來指向的那個堆空間 // 如果先刪除m_pointer指向的堆空間,就有可能導致異常丟擲 // 要保證異常安全性 // delete m_pointer; // 指向新的堆空間 // m_pointer = obj.m_pointer; // 刪除obj物件中m_pointer與這個堆空間的關聯,保證一個堆空間只有一個指標指向這個堆空間 // const_cast<SmartPointer<T>&>(obj).m_pointer = NULL; // 為了異常安全性,用一個臨時變數儲存this->pointer指標,方便釋放 T* p = this->m_pointer; this->m_pointer = obj.m_pointer; const_cast<SmartPointer<T>&>(obj).m_pointer = NULL; delete p; } return *this; } // 解構函式需要衝重寫,否則就還是一個抽象類 ~SmartPointer() { delete this->m_pointer; } }; } #endif // SMARTPOINTER_H
2、 SharedPointer
智慧指標
需求:多個智慧指標指向同一片堆空間,並且這些指標支援自動釋放
SharedPointer
設計要點:類模板
ref ref++ ref-- ref == 0
計數機制原理剖析:
3個指標同時指向了堆空間中的統一物件,與物件相關聯的計數標識應該是3。如果將 shared_pointer_3
置空,應該計數減一,計數變數為2;如果全部置空,計數變數為0,意味著最後一個智慧指標要將堆空間裡面的物件銷燬掉,將堆空間的記憶體釋放。
虛線矩形框將物件和計數變數框在了一起,意味著每一個堆空間中的物件都和這個計數變數相關聯,這個計數變數也位於堆空間裡面。在具體實現上計數變數也是在堆空間裡面建立的,並且計數變數的生命週期和這個物件的生命週期是完全一致的。
SharedPointer
類的宣告
template <typename T> class SharedPointer : public Pointer<T> { protected: int* m_ref; // 計數機制成員指標 // 成員指標指向堆空間裡面建立的計數變數 public: SharedPointer(T* p = NULL); SharedPointer(const SharedPointer<T>& obj); SharedPointer<T>& operator = (const SharedPointer<T>& obj); void clear();// 當前指標置空 ~SharedPointer(); };
由於 SharedPointer
支援多個物件同時指向一片堆空間,因此必須支援比較操作,使智慧指標最大限度上接近原生指標的邏輯。
具體實現如下:
// SharedPointer.h #ifndef SHAREDPOINTER_H #define SHAREDPOINTER_H #include <cstdlib> #include "Exception.h" #include "Pointer.h" namespace DTLib { template <typename T> class SharedPointer : public Pointer<T> { protected: int* m_ref; // 計數機制成員指標 // 進行函式封裝 void assign(const SharedPointer<T>& obj) { this->m_ref = obj.m_ref;// 將當前指標物件的ref成員指標指向了對應的計數物件 this->m_pointer = obj.m_pointer;// m_pointer指向對應的堆記憶體 // 還不夠,注意計數機制,計數變數需要+1 if (this->m_ref) {// 計數變數合法 (*this->m_ref)++; } } public: // 首先是建構函式對成員變數初始化 SharedPointer(T* p = NULL) : m_ref(NULL)// 將指向計數變數的成員指標,初始化為空 { if (p) { // 首先在堆空間中建立一個計數變數 // 在堆空間中申請4個位元組空間作為存放計數變數的記憶體空間 this->m_ref = static_cast<int*>(std::malloc(sizeof(int)));// malloc返回型別是void* // 判斷申請是否成功 if (this->m_ref) { *(this->m_ref) = 1;// 意味著引數指標p指向的堆空間已經有了一個SharedPointer智慧指標物件來指向了 this->m_pointer = p;// 將成員指標變數指向引數p對應的堆空間 } else {// malloc不成功,意味著記憶體不夠用,拋異常 THROW_EXCEPTION(NoEnoughMemoryException, "No memory to creat SharedPointer object..."); } } } SharedPointer(const SharedPointer<T>& obj) { //this->m_ref = obj.m_ref;// 將當前指標物件的ref成員指標指向了對應的計數物件 //this->m_pointer = obj.m_pointer;// m_pointer指向對應的堆記憶體 //// 還不夠,注意計數機制,計數變數需要+1 //if (this->m_ref) //{// 計數變數合法 //(*this->m_ref)++; //} assign(obj); } // 賦值操作符過載函式 SharedPointer<T>& operator = (const SharedPointer<T>& obj) { if (this != &obj) {// 避免自賦值 // 邏輯與拷貝構造類似,但是需要做清空操作 // 當前的SharedPointer物件已經指向了另一片堆空間了,在做賦值操作前,應該將當前的智慧指標物件置空,不再指向任何堆空間 // 在賦值之前,置空 clear() clear(); //// 可以程式碼複用,封裝內部函式 //this->m_ref = obj.m_ref; //this->m_pointer = obj.m_pointer; //if (this->m_ref) //{ //(*this->m_ref)++; //} assign(obj); } return *this; } void clear()// 當前指標置空 { T* toDel = this->m_pointer; int* ref = this-> m_ref; this->m_pointer = NULL; this->m_ref = NULL; if (ref) {// 當前計數變數合法 (*ref)--; if (*ref == 0) {// 為0標識該堆空間已經沒有任何智慧指標物件去指向了,應該釋放該堆空間 free(ref);// 釋放計數變數 delete toDel;// 釋放堆空間 } } } ~SharedPointer() { clear(); } }; } #endif // SHAREDPOINTER_H
測試:
int main() { SharedPointer<Test> sp0 = new Test(); SharedPointer<Test> sp1 = sp0; SharedPointer<Test> sp2 = NULL; sp2 = sp1; sp2->value = 100; cout << sp0->value << endl; cout << sp1->value << endl; cout << sp2->value << endl; cout << (sp0 == sp2) << endl; return 0; }
結果:
Test()
100
100
100
0
~Test()
sp0 sp1 sp2
均指向了同一片堆空間,通過 sp2->value
更改值之後,和原生指標效果一樣,但是進行指標比較的時候,需要過載比較操作符
#ifndef SHAREDPOINTER_H #define SHAREDPOINTER_H #include <cstdlib> #include "Exception.h" #include "Pointer.h" namespace DTLib { template <typename T> class SharedPointer : public Pointer<T> { ... }; // 在全域性區過載比較操作符 template <typename T> bool operator == (const SharedPointer<T>& l, const SharedPointer<T>& r) { return (l.get() == r.get()); // get()函式不是const成員函式,所以不能被const物件呼叫 } template <typename T> bool operator != (const SharedPointer<T>& l, const SharedPointer<T>& r) { return !(l == r);// !=操作符過載的實現用上面==操作的實現就可以了 } } #endif // SHAREDPOINTER_H
智慧指標的使用規則:
delete
3、小結
SharedPointer
最大程度地模擬了原生指標的行為
計數機制確保多個智慧指標合法地指向同一片堆空間
智慧指標只能用於指向堆空間中的記憶體
不同型別的智慧指標不要混合使用
堆物件的生命週期由智慧指標進行管理