1. 程式人生 > >十三、智慧指標二

十三、智慧指標二

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最大程度地模擬了原生指標的行為

計數機制確保多個智慧指標合法地指向同一片堆空間

智慧指標只能用於指向堆空間中的記憶體

不同型別的智慧指標不要混合使用

堆物件的生命週期由智慧指標進行管理