筆記十一:智慧指標(一)
前言:淺拷貝容易出現對同一記憶體空間進行2次撤銷,造成程式崩潰。於是,我們可以利用指標智慧來解決這一問題。本節主要介紹利用使用計數類構造智慧指標類。
使用計數: 智慧指標將一個計數器與類指向的物件相關聯。使用計數跟蹤該類有多少個物件共享同一指標。使用計數為0時,刪除物件。
規則:
1、每次建立類的新物件時,初始化指標並將計數器置1
(計數器置1的原因是在執行解構函式時,計數器先減1,然後再判斷是否為0進行記憶體撤銷,假設建立了一個新物件不進行任何操作直接進行析構,則減1載判斷是否為0,那麼計數器初始值就必須為1才能記憶體撤銷。)
2、當物件作為另一個物件的副本而建立時,複製建構函式複製指標並增加與之相應的使用計數的值
(物件增加,用於跟蹤該類有多少個物件的引用計數當然要隨之增加)
3、對一個物件進行賦值時,賦值操作符減少左操作所指物件的使用計數值(若使用計數減至為0,則刪除物件),並增加右運算元所指物件的使用計數的值。
(減少左運算元所指物件的使用計數,是因為左操作由原來指向a物件改為指向b物件,那麼共享a的物件的數目自然要減少,同理b物件的共享物件的數目要相應增加。假設是同一物件的自我賦值操作,也滿足上述原理。)
例1:不同物件的賦值
int *p = new int(7);
int *q = new int(8);
SmartPtr<int> SP(p, 0);
cout << "SP物件原始指向的物件:"<< SP.get_ptr_val() << endl;
SmartPtr<int> SP1(q, 0);
SP = SP1;
cout << "SP = SP1後指向的物件:" << SP.get_ptr_val() << endl;
例2:同一物件的自我賦值
4、呼叫解構函式時,減少使用計數的值,若計數減至0,則刪除基礎物件
(析構表明該類資源被釋放,則指向基礎物件的共享物件少了一個,使用計數當然減1了)
實現方式一: 定義一個單獨的使用計數類
template<class T> class SmartPtr;
//引用計數類
template<class T>
class U_Ptr
{
friend class SmartPtr<T>;
U_Ptr(T *p) : ip(p), use(1) { }
~U_Ptr() { delete ip;}
private:
T *ip; //指向共享物件的指標
size_t use; //引用計數
};
——類的所有成員均為private,是不希望普通使用者可以使用U_Ptr類,將SmartPtr類設定為友元,是其可以訪問 U_Ptr類。
——友元模板類的使用規則
方式一:在類之前宣告模板類
template<class T> class B;
tempate<class T> A
{
friend class B<T>;
};
方式二:在類中說明類為模板類
template<class T>
class A
{
tempalte<class T> friend class B;
};
使用計數工作原理圖:
1、U_Ptr類儲存指標和使用計數,每個SmartPtr物件指向一個U_Ptr物件
2、使用計數跟蹤指向每個U_Ptr物件的SmartPtr物件的數目。
智慧指標類SmartPtr
/智慧指標類
template<class T>
class SmartPtr
{
public:
SmartPtr(T *p, T i) : ptr(new U_Ptr<T>(p)), val(i) { }
SmartPtr(const SmartPtr &orig) : ptr(orig.ptr), val(orig.val) { ++ptr->use; } //複製建構函式,使用計數加1
SmartPtr<T>& operator=(const SmartPtr& rhs);
~SmartPtr() {
if (--ptr->use == 0)
delete ptr;
}
public:
T get_ptr_val() const
{
int a = *ptr->ip;
return *ptr->ip;
}
void set_ptr_val(T i){ *ptr->ip = i; }
private:
U_Ptr<T> *ptr; //指向U_ptr物件的指標
T val;
};
template<class T>
SmartPtr<T>& SmartPtr<T>::operator=(const SmartPtr& rhs)
{
++rhs.ptr->use; //右運算元使用計數加1
if(--ptr->use == 0) //左運算元使用計數減1
{
delete ptr;
}
ptr = rhs.ptr;
val = rhs.val;
return *this;
}
—— ptr(new U_Ptr<T>(p))
使用指標形參p建立一個新的U_Ptr物件,其中p為SmartPtr共享的基礎物件指標
——SmartPtr(const SmartPtr &orig)
複製建構函式
——SmartPtr<T>& operator=
賦值操作符
——return *this;
this為指向該類或物件的指標,故*this表示為該物件
——複製控制成員:複製建構函式、賦值操作符、解構函式
——三法則:如果一個類需要解構函式,則該類幾乎也必然需要定義自己的複製建構函式和賦值操作符
——無論類是否定義了自己的解構函式,都會建立和執行合成解構函式。如果類定義了解構函式,則類定義的解構函式結束之後執行合成解構函式。
注意要點:
~U_Ptr() { delete ip;}
解構函式中,使用delete對ip進行銷燬,表明ip指向一個動態分配的空間,而ip的初始化由ptr(new U_Ptr<T>(p))
完成,表明p指向的記憶體空間必須是動態分配的。
1)p指向的記憶體空間是動態分配的,
int *p = new int(7);
SmartPtr<int> SP(p, 0);
cout << SP.get_ptr_val() << endl;
2、p指向的空間是靜態分配的
int a = 7;
int *p = &a;
SmartPtr<int> SP(p, 0);
cout << SP.get_ptr_val() << endl;
這是因為int a=7;
的記憶體空間是編譯器在棧區中自動分配的,由編譯器自動完成分配和回收操作,無需程式設計師delete操作。此時ip恰好指向a變數的記憶體地址,U_Ptr在析構時delete ip就會出現錯誤。
若修改U_Ptr的解構函式為~U_Ptr() { /*delete ip;*/}
則執行正常。
由圖可知,ip與p儲存的地址值相同,即共同指向基礎物件。use = 2 表明有2個共享基礎物件的SmartPtr物件,與程式中建立一個新物件SP,加上用SP複製的一個SP1物件的數目吻合。