十三、智慧指標二
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
最大程度地模擬了原生指標的行為計數機制確保多個智慧指標合法地指向同一片堆空間
智慧指標只能用於指向堆空間中的記憶體
不同型別的智慧指標不要混合使用
堆物件的生命週期由智慧指標進行管理