1. 程式人生 > >智慧指標(shared_ptr的實現)

智慧指標(shared_ptr的實現)

1.實現原理:shared_ptr是利用一個計數器,無論我們使用拷貝建構函式、賦值運算子過載、作為函式返回值、或作為引數傳給一個引數時計數器+1,
當shared_ptr被賦予一個新值或者需要銷燬時,計數器–,直到計數器為0時,呼叫解構函式,釋放物件,並銷燬其記憶體。shaerd_ptr不直接支援管理動態陣列,如果希望使用shared_ptr管理一個動態陣列,必須定製自己的刪除器。

class SharedPtr
{
public:
        SharedPtr(T*ptr=NULL)
               :_ptr(ptr)
               , _pcount(new int(1))
        {}
        SharedPtr(const SharedPtr&s)
               :_ptr(s._ptr)
               , _pcount(s._pcount)
        {
               *(_pcount)++;
        }
        SharedPtr<T>&operator=(const SharedPtr&s)
        {
               if (this!= &s)
               {
                       if (--(*(this->_pcount)) == 0)
                       {
                              delete this->_ptr;
                              delete this->_pcount;
                       }
                       _ptr = s._ptr;
                       _pcount = s._pcount;
                       *(_pcount)++;
               }
               return *this;
        }
        T&operator*()
        {
               return *(this->_ptr);
        }
        T*operator->()
        {
               return this->_ptr;
        }
        ~SharedPtr()
        {
               --(*(this->_pcount));
               if (this->_pcount == 0)
               {
                       delete _ptr;
                       _ptr = NULL;
                       delete _pcount;
                       _pcount = NULL;
               }
        }
private:
        T*_ptr;
        int *_pcount;//指向引用計數的指標
};
int main()
{
        SharedPtr<int>p1(new int(1));
        SharedPtr<int>p2(p1);
        SharedPtr<int> p3 = p2;//呼叫的是拷貝建構函式、因為p3原本是不存在的。
        p3 = p2;
        system("pause");
        return 0;
}

2.但是其存在一些問題:
問題一:迴圈引用問題:

template<class T>
struct ListNode
{
        ListNode(T value)
        :_value(value)
        {
               cout << "ListNode()" << endl;
        }
        ~ListNode()
        {
               cout << "~ListNode()" << endl;
        }
        T _value;
        shared_ptr<ListNode<T>> _prev;
        shared_ptr<ListNode<T>> _next;
};
void test()
{
        shared_ptr<ListNode<int>> p1(new ListNode<int>(1));
        shared_ptr<ListNode<int>> p2(new ListNode<int>(2));
        cout << p1.use_count() << endl;
        cout << p2.use_count() << endl;
        p1->_next = p2;
        p2->_prev = p1;
        cout << p1.use_count() << endl;
        cout << p2.use_count() << endl;
}
int main()
{
        test();
        system("pause");
        return 0;
}

當資源要釋放時,p1節點釋放的前提是p2釋放,而p2的釋放又依賴於p1,就形成了一個互相等待的局面,上升到作業系統的話,就等於程序之間形成了死鎖,只不過這裡是資源釋放的依賴關係,而作業系統是資源競爭的關係。最終程式形成了迴圈引用,兩個節點都無法釋放資源,記憶體洩漏也就順理成章。
問題一:迴圈引用問題
解決辦法:所以此時需要利用weak_ptr來解決迴圈引用的問題,weak_ptr它指向的是一個由shared_ptr管理的物件,將一個weak_ptr繫結到一個shared_ptr的物件上,其不會改變shared_ptr的引用計數,一旦最後一個指向物件的shared_ptr被銷燬,物件就會被銷燬,即使有weak_ptr指向物件,物件還是被釋放。

template<class T>
struct ListNode
{
        ListNode(T value)
        :_value(value)
        {
               cout << "ListNode()" << endl;
        }
        ~ListNode()
        {
               cout << "~ListNode()" << endl;
        }
        T _value;
        weak_ptr<ListNode<T>> _prev;
        weak_ptr<ListNode<T>> _next;
};
void test()
{
        shared_ptr<ListNode<int>> p1(new ListNode<int>(1));
        shared_ptr<ListNode<int>> p2(new ListNode<int>(2));
        cout << p1.use_count() << endl;
        cout << p2.use_count() << endl;
        p1->_next = p2;
        p2->_prev = p1;
        cout << p1.use_count() << endl;
        cout << p2.use_count() << endl;
}
int main()
{
        test();
        system("pause");
        return 0;
}

weak_ptr是一個不控制物件生命週期的智慧指標,它指向一個由shared_ptr指向的物件,將一個weak_ptr繫結到由shared_ptr指向的物件上,它不會改變shared_ptr的引用計數,當引用計數等於0時,
物件就會被銷燬,呼叫解構函式,即使weak_ptr指向物件,物件還是會釋放。
問題二:執行緒安全問題
shared_ptr物件提供與內建型別一致的執行緒安全級別,一個shared_ptr指向的物件可以被多個執行緒進行“讀”,一個shared_ptr指向的物件可以被多個執行緒寫入,雖然這些看似是拷貝,但是導致執行緒不安全。(即使這些實 例是拷貝,而且共享下層的引用計數),任何其它的同時訪問的結果會導致未定義行為。總結一下主要有3個方面。

1.同一個shared_ptr被多個執行緒“讀“是安全的。
2.同一個shared_ptr被多個執行緒“寫”是不安全的。
3.共享引用計數不同的shared_ptr被多個執行緒“寫”是安全的。

問題三:記憶體洩漏問題
當我們用malloc申請出來的空間是無法釋放的,因為malloc申請的空間只能用free來釋放,而當我們開啟一個檔案指標,程式執行完畢後,需要關閉檔案,否則會造成記憶體洩漏。
所以要來定製刪除器,定製刪除器還有一個原因是shared_ptr不支援動態陣列管理,若要管理動態陣列,則需自己定製刪除器。