1. 程式人生 > >筆記十一:智慧指標(一)

筆記十一:智慧指標(一)

前言:淺拷貝容易出現對同一記憶體空間進行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物件的數目吻合。