1. 程式人生 > >智慧指標(下)-----boost庫智慧指標,定製刪除器、迴圈引用

智慧指標(下)-----boost庫智慧指標,定製刪除器、迴圈引用

上一篇我們已經詳細講解了智慧指標的基礎性知識和auto_ptr的模擬實現。
今天呢我們來講解boost庫的發展。
在C++11標準出來之前,C++98標準中都一直只有一個智慧指標auto_ptr,我們知道,這是一個失敗的設計。它的本質是管理權的轉移,這有許多問題。而這時就有一群人開始擴充套件C++標準庫的關於智慧指標的部分,他們組成了boost社群,他們負責boost庫的開發和維護。其目的是為C++程式設計師提供免費、同行審查的、可移植的程式庫。boost庫可以和C++標準庫完美的共同工作,並且為其提供擴充套件功能。現在的C++11標準庫的智慧指標很大程度上“借鑑”了boost庫。

1,scoped_ptr的模擬實現


scoped_ptr是一種簡單粗暴的設計,它本質就是防拷貝,避免出現管理權的轉移。這是它的最大特點,所以他的拷貝構造和賦值運算子過載只是只宣告不定義,但是為了防止有的人在類外定義,所以將函式宣告為protected。但這則也是他最大的問題所在,就是不能賦值拷貝,也就是說功能不全。

template<class T>
class ScopedPtr
{
public:
    ScopedPtr(T* ptr = NULL)
        :_ptr(ptr)
    {
        cout << "ScopedPtr" << endl;
    }

    ~ScopedPtr()
    {
        delete
_ptr; cout << "~ScopedPtr" << endl; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } bool operator==(const ScopedPtr<T>& s) { return _ptr == s._ptr; } bool operator!=(const
ScopedPtr<T>& s) { return _ptr != s._ptr; } void Reset(T* ptr = NULL) { if (_ptr != ptr) { delete _ptr; } _ptr = ptr; } protected: ScopedPtr(ScopedPtr<T>& s); //防拷貝(只宣告不定義,為防止別人在類外定義,就將他宣告為protected) ScopedPtr<T>& operator=(ScopedPtr<T>& s); protected: T* _ptr; };

2,shared_ptr的模擬實現
這是比較完善的一個智慧指標,他是通過指標保持某個物件的共享擁有權的智慧指標。若干個shared_ptr物件可以擁有同一個物件,該物件通過維護一個引用計數,記錄有多少個shared_ptr指標指向該物件,最後一個指向該物件的shared_ptr被銷燬或重置時,即引用計數變為0時,該物件被銷燬。銷燬物件時使用的是delete表示式或是在構造shared_ptr時傳入的自定義刪除器(delete),這後面會有詳細講解,但是shared_ptr指標同樣擁有缺陷,那就是迴圈引用,和執行緒安全問題,這也在後面講解。先來模擬實現一下shared_ptr指標。

template<class T>
class SharedPtr
{
public:
    SharedPtr(T* ptr = NULL)
        :_ptr(ptr),_count(new int(0))
    {
        if (_ptr != NULL)
        {
            (*_count)++;
        }
    }

    SharedPtr(const SharedPtr<T>& s)
    {
        _ptr = s._ptr;
        _count = s._count;
        if (_ptr != NULL)
        {
            (*_count)++;
        }
    }

    SharedPtr<T>& operator=(const SharedPtr<T>& s)
    {
        if (this != &s)   //排除自己給自己賦值
        {
            if (--(*_count) <= 0)  //正常情況賦值
            {
                delete _ptr;
                delete _count;
            }
            else  //指向同一個物件的指標互相賦值
            {}
            _ptr = s._ptr;
            _count = s._count;
            (*_count)++;
        }
        return *this;
    }

    ~SharedPtr()
    {
        if (--(*_count) == 0)
        {
            delete _ptr;
            delete _count; //別忘了delete維護的引用計數
        }
    }

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }

    bool operator ==(const SharedPtr<T>& S)
    {
        return (_ptr == s._ptr);
    }

    bool operator !=(const SharedPtr<T>& S)
    {
        return (_ptr != s._ptr);
    }

protected:
    T* _ptr;
    int* _count; 
};

執行緒安全問題
因為使用引用計數值位判定指標,所以在多執行緒的環境下是不安全的。會因執行緒呼叫的先後順序不同導致錯誤產生。對於這種問題,解決方法一般是加鎖,對引用計數進行加,保證操作是互斥的。(這裡暫且說這些,後續文章會有提及。)

迴圈引用問題
針對迴圈引用,我來舉個例子使大家能更好的理解。看下面程式碼:

struct ListNode
{
    int _data;
    shared_ptr<ListNode> _next;
    shared_ptr<ListNode> _prev;

    ListNode(int x)
        :_data(x),_next(NULL),_prev(NULL)
    {}
    ~ListNode()
    {
        cout << "~ListNode()" << endl;
    }
};

void test()
{
    shared_ptr<ListNode> A(new ListNode(1));
    shared_ptr<ListNode> B(new ListNode(2));

    //if (A && B)  //將這五行程式碼放開來會出現什麼情況
    //{
    //  A->_next = B;
    //  B->_prev = A;
    //}

    cout << "A._count:" << A.use_count() << endl;
    cout << "B._count:" << B.use_count() << endl;
}

int main()
{
    test();
    system("pause");
    return 0;
}

執行結果
這裡寫圖片描述

但是如果我將上面程式碼中遮蔽的那五行放開來,會出現什麼結果呢?看下圖:
這裡寫圖片描述
有什麼不同?對比上圖,可以發現下面圖的兩個節點維護的引用計數值為2,他們也沒有呼叫解構函式造成記憶體洩露。這是什麼原因造成的?我們用一張圖來解釋。
這裡寫圖片描述
而要解決迴圈引用的問題,就牽扯到了我們後面將要講的一個指標weak_ptr。具體看後面。

定製刪除器(仿函式)
經上面分析,我們可以看到,上面的指標不能用於檔案的關閉,也不能用於管理mallocnew[]開闢的動態記憶體的釋放,所以我們可以運用仿函式來定製刪除器。如下:

template<class T>
struct DeleteArray  //用於new[]開闢的動態記憶體釋放
{
    void operator()(T* ptr)
    {
        cout << "A" << endl;
        delete[] ptr;
    }
};

struct Fclose  //用於檔案關閉
{
    void operator()(FILE* ptr)
    {
        cout << "B" << endl;

        fclose(ptr);
    }
};

template<class T>
struct Free     //用於malloc開闢的動態記憶體的釋放
{
    void operator()(T* ptr)
    {
        cout << "C" << endl;
        free(ptr);
    }
};

int main()
{
    shared_ptr<string> ap1(new string[10], DeleteArray<string>());
    shared_ptr<FILE> ap2(fopen("test.txt", "w"),Fclose());
    shared_ptr<int> ap3((int*)malloc(sizeof(int)), Free<int>());
    return 0;
}

3,weak_ptr
weak_ptr是一個輔助性的智慧指標,結合shared_ptr指標使用,它的本質就是弱引用,並不增加引用計數值。他沒有實現->和*運算子的過載,所以不能直接用它訪問物件。針對迴圈引用這個問題,就是因為不會引起引用計數值的改變,所以我們可以將_next和_prev定義為weak_ptr指標,這樣就很好地解決了迴圈引用的問題。

struct ListNode
{
    int _data;
    weak_ptr<ListNode> _next;  //定義為weak_ptr指標
    weak_ptr<ListNode> _prev;

    ListNode(int x)
        :_data(x),_next(NULL),_prev(NULL)
    {}
    ~ListNode()
    {
        cout << "~ListNode()" << endl;
    }
};

boost庫剩餘的兩個指標:auto_arrshared_arr.這兩個都是管理陣列的,因為之前幾個指標的解構函式中都是delete,不能對陣列進行釋放,所以我們自己定製刪除器,而這兩個指標就是專門管理指標的。下面來模擬實現一下。

模擬實現auto_arr

template<class T>
class AutoArr
{
public:
    AutoArr(T* ptr = NULL)
        :_ptr(ptr)
    {}

    ~AutoArr()
    {
        delete[] _ptr;
    }

    AutoArr(const AutoArr<T>& s)
    {
        _ptr = s._ptr;
        s._ptr = NULL;
    }

    AutoArr<T>& operator=(const AutoArr<T>& s)
    {
        if (this != &s)
        {
            _ptr = s._ptr;
            s._ptr = NULL;
        }
        return *this;
    }

    T& operator[](size_t pos)
    {
        if (_ptr == NULL)
        {
            throw a;
        }
            return *(_ptr+pos);
    }

    void set(T* ptr)
    {
        int i = 0;
        while (*(ptr + i))
        {
            *(_ptr + i) = *(ptr + i);
            i++;
        }
    }

protected:
    T* ptr;
};

模擬實現shared_arr

template<class T>

class SharedArr
{
public:
    SharedArr(T* ptr = NULL)
        :_ptr(ptr),_count(new int(0))
    {
        (*_count)++;
    }

    ~SharedArr()
    {
        delete[] _ptr;
    }

    SharedArr(const SharedArr<T>& s)
    {
        _ptr = s._ptr;
        (*_count)++;
    }

    SharedArr<T>& operator=(const SharedArr<T>& s)
    {
        if (this != &s)
        {
            if (--(*_count) <= 0)
            {
                delete _ptr;
                delete _count;
            }
            else
            { }
            _ptr = s._ptr;
            _count = s._count;
            (*_count)++;
        }
    }

    T& operator[](size_t pos)
    {
        if (_ptr == NULL)
        {
            throw 1;
        }
        return *(_ptr + pos);
    }

protected:
    T* _ptr;
    int* _count;
};