1. 程式人生 > >詳細分析智慧指標shared_ptr存在的迴圈引用缺陷,以及通過weak_ptr來解決

詳細分析智慧指標shared_ptr存在的迴圈引用缺陷,以及通過weak_ptr來解決

模擬實現的簡單的shared_ptr:

template <class T>
class SharedPtr{
public:
    SharedPtr(T* ptr)
        :_ptr(ptr), _refCount(new int(1))
        {}

    SharedPtr(SharedPtr<T>& sp)
        :_ptr(sp._ptr), _refCount(sp._refCount)
    {
        ++(*_refCount);
    }
    SharedPtr<T>& operator=(SharedPtr
<T>& sp)
{ if (_ptr != sp._ptr){ if (--(*_refCount) == 0){ delete _ptr; delete _refCount; } _ptr = sp._ptr; _refCount = sp._refCount; ++(*_refCount); } return *this; } T
& operator*() { return *_ptr; } T* operator->() { return _ptr; } ~SharedPtr() { if (--(*_refCount) == 0){ delete _ptr; delete _refCount; } } private: T* _ptr; int* _refCount; };

shared_ptr不僅體現了RAII思想,而且利用引用計數,解決了淺拷貝的問題。
至於它的問題,需要特定的場景。

建立一個場景:

struct ListNode
{
    ListNode()
        :_data(1)
        , _next(NULL)
        , _prev(NULL)
    {}

    int _data;
    SharedPtr<ListNode> _next;
    Shared_ptr<ListNode> _prev;
};

這是一個雙向連結串列,next和prev都是SharedPtr智慧指標,也就是說_next和_prev裡不僅有一個ListNode*的指標,還有一個引用計數。
接下來例項化出物件:

SharedPtr<ListNode> cur(new ListNode);
SharedPtr<ListNode> next(new ListNode);

先來說兩個SharedPtr物件:cur和next的物件模型:
首先我們建立了兩個SharedPtr物件:cur,next,所以兩個物件必須都各自有一個ListNode*類的指標和一個引用計數;
這裡寫圖片描述
這兩個物件的ListNode*類指標自然指向ListNode物件,引用計數初始化為1;
這裡寫圖片描述

接下來需要說ListNode的物件模型:
由於使用得是new,所以開闢空間的同時還呼叫了ListNode的建構函式:
這裡寫圖片描述
而在ListNode的建構函式裡又分別呼叫了_next和_prev的建構函式:
這裡寫圖片描述
所以說ListNode物件模型就是:兩個SharedPtr物件_prev和_next,物件的_ptr被初始化為NULL,引用計數被初始化為0,另外還有一個數據_data:
這裡寫圖片描述

綜上,cur和next物件的_ptr分別指向了一個ListNode物件,而ListNode物件內有兩個智慧指標_prev和_next,它們分別又有一個_ptr和一個引用計數,_ptr被初始化為NULL,引用計數被初始化為1。
如下圖所示:
這裡寫圖片描述

接下來賦值:

    cur->_next = next;
    next->_prev = cur;

在這裡是呼叫了SharedPtr的operator=,把next賦值給了cur的_next,把cur賦值給了next的_prev:
這裡寫圖片描述

接下來通過監視看:

SharedPtr<ListNode> cur(new ListNode);
SharedPtr<ListNode> next(new ListNode);

這兩句程式碼構造了兩個物件cur和next,它們分別有_ptr和_refCount,它們的_ptr指向一個ListNode型別的物件,_*refcount初始化為1。
兩個ListNode物件內有分別有兩個物件_next和_prev,它們的_ptr初始化為NULL,_refcount為1
請看監視:
這裡寫圖片描述

cur->_next = next;
next->_prev = cur;

呼叫了operator=,因為_next和_prev已經被初始化。
所以說,在operaotor=函式裡,我已經標明瞭指向關係,並且現在cur和next的引用計數都變為2。
請看監視:
這裡寫圖片描述

存在問題:
出了cur和next作用域,會呼叫解構函式,那麼會先呼叫_next的解構函式,再呼叫_cur的解構函式,但是這會陷入一個迴圈,因為釋放關係中你的釋放需要我,我的釋放需要你,誰都不肯放手,自然會出現記憶體洩漏。

如何解決問題,引入了wead_ptr,這裡我也是簡單的模擬實現它:

template <class T>
class WeakPtr
{
public:
    WeakPtr(const SharedPtr<T>& sp)
        :_ptr(sp._ptr)
    {}

    WeakPtr<T>& operator=(SharedPtr<T>& sp)
    {
        _ptr = sp._ptr;
        return *this;
    }

private:
    T* _ptr;
};

然後對以上程式碼進行修改:

struct ListNode
{
    ListNode()
        :_data(1)
        , _next(NULL)
        , _prev(NULL)
    {}

    int _data;
    WeakPtr<ListNode> _next;
    WeakPtr<ListNode> _prev;
};

那麼

void TestSharedPtrError()
{
    SharedPtr<ListNode> cur(new ListNode);
    SharedPtr<ListNode> next(new ListNode);
    cur->_next = next;
    next->_prev = cur;
}

就是Shraed_ptr的物件賦值給WeakPtr的物件。
這裡寫圖片描述
這樣的話出了作用域呼叫解構函式,就可以清理乾淨,不會存在記憶體洩漏。

相關推薦

詳細分析智慧指標shared_ptr存在的迴圈引用缺陷以及通過weak_ptr解決

模擬實現的簡單的shared_ptr: template <class T> class SharedPtr{ public: SharedPtr(T* ptr) :_ptr(ptr), _refCount(new in

記一次由於智慧指標shared_ptr迴圈引用而產生的C++記憶體洩漏

自從 C++ 11 以來,boost 的智慧指標就被加入了 C++ 新標準之中。其中,廣為人知的 shared_ptr 被用的最多,以引用計數的方式來管理指標指向資源的生命週期。看起來有了智慧指標後,C++ 程式再也不用擔心記憶體洩漏了,就可以像 Java 一樣

boost庫在工作(9)引用計數的智慧指標shared_ptr之二

接著下來,需要演示一下怎麼樣在多個物件裡共享一個物件,而不管每個物件的生命週期,就可以及時地把使用的物件在合適地方刪除。下面的例子裡先定義兩個類,然後每個類都引用共享的物件,接著使用完成後,就會在解構函式裡刪除,可以從例子程式執行的輸出結果看到內部執行的生命週期。有了共享智慧

c++11: 智慧指標 shared_ptr & unique_ptr

一、背景 1. 堆記憶體、棧記憶體、靜態區記憶體 我們知道,靜態記憶體用來儲存區域性 static 物件、類 static 資料成員以及定義在函式之外的變數。而棧記憶體用來儲存定義在函式內的非 static 物件。 分配在靜態區或棧記憶體中的物件由編譯器自動建立和銷燬,對於棧

智慧指標shared_ptr的實現

#include <iostream> using namespace std; template <class T> class smart { public: smart() = default; smart(T* ptr = nullptr):_ptr(

C++Boost庫學習之智慧指標 shared_ptr

目錄 1.共享指標shared_ptr ^   使用例子 ^ #include<boost/shared_ptr.hpp> using namespace boost; using std::cout; using std::endl; str

C++智慧指標shared_ptr講解與使用

手動管理的弊端 在簡單的程式中,我們不大可能忘記釋放 new 出來的指標,但是隨著程式規模的增大,我們忘了 delete 的概率也隨之增大。在 C++ 中 new 出來的指標,賦值意味著引用的傳遞,當賦值運算子同時展現出“值拷貝”和“引用傳遞”兩種截然不同的語義

C++11--智慧指標shared_ptrweak_ptrunique_ptr

共享指標 shared_ptr /*********** Shared_ptr ***********/ // 為什麼要使用智慧指標,直接使用裸指標經常會出現以下情況 // 1. 當指標的生命長於所指的資源:野指標 // 2. 當指標的生命短於所指的資源:資源洩漏 // // 智慧指標: 確保指標和資源的生

C++ 11 智慧指標的坑和引用計數的意義

一、本節內容本節內容包括:對標準庫的擴充: 智慧指標和引用計數RAII 與引用計數std::shared_ptrstd::unique_ptrstd::weak_ptr二、RAII 與引用計數瞭解 Objective-C/Swift 的程式設計師應該知道引用計數的概念。引用計

C++智慧指標shared_ptr使用例項

被new/delete折磨過之後,才能真正體會智慧指標有多好用。 智慧指標是用於管理指標的類。 其中shared_ptr是專門管理那些可能被多個地方用到的指標的類。 C++11中智慧指標的標頭檔案是: #include <memory>

智慧指標shared_ptr與unique_ptr詳解

為什麼使用動態記憶體 程式不知道自己需要多少物件; 程式不知道物件的準確型別; 程式需要在多個物件之間共享資料; 動態記憶體在哪裡 程式有靜態記憶體、棧記憶體。靜態記憶體用來儲存區域性static物件、類static資料成員以及定義在任何函式之外的

智慧指標shared_ptr 的簡單實現

#include<iostream> using namespace std; class U_Ptr {friend class HasPtr;int *ip;size_t use;U_Ptr(int *p) :ip(p), use(1){cout <&

c++:分析智慧指標與發展歷史

智慧指標是RAII思想的一種產品,那麼什麼是RAII呢? RAII:Resource Acquistion Is Initialization 資源分配就初始化,定義一個類來封裝資源的分配和釋放。 在建構函式裡完

boost庫的智慧指標shared_ptr結合容器vector的使用

將n個shared_ptr放在vector中,vector會保持每個shared_ptr的引用;vector銷燬時,shared_ptr會自動銷燬所持物件,釋放記憶體 #include <ios

C++ 智慧指標shared_ptr模板實現原理

C++ 智慧指標shared_ptr通過引用計數來管理指向的物件,不需要人工釋放 這篇博文主要是講解了智慧指標的實現原理,怎麼實現引用計數 #include <iostream> using namespace std; namespace shao_sma

C++利用智慧指標shared_ptr實現物件池

         C++中用new來分配物件,還要顯式的呼叫delete來析構物件,很容易造成記憶體洩露。所以在研究我們遊戲伺服器的程式碼的時候發現,我們將new函式封裝,在物件池上,利用shared_ptr的特性來自動釋放物件,同時實現了一種簡單的GC回收物件的機制。看完

智慧指標 shared_ptr

shared_ptr:  shared_ptr是一個最像指標的"智慧指標".  shared_ptr與scoped_ptr一樣包裝了new操作符在堆上分配的動態物件,但它實現的是引用計數型的智慧指標,可以被自由的拷貝和賦值,在任意的地方共享它,當沒有程式碼使用(引用計數為0

智慧指標的使用條件和缺陷

一、auto_ptr 不要使用兩個auto_ptr指標指向同一個指標; 不要使用auto_ptr指向一個指標陣列,因為auto_ptr的解構函式所用的是delete而不是delete[],不匹配; 不要將auto_ptr儲存在容器中,因為賦值和拷貝構造後原指標無法使用。

JavaScipt 中的事件迴圈(event loop)以及微任務 和巨集任務的概念

說事件迴圈(event loop)之前先要搞清楚幾個問題。 1. js為什麼是單執行緒的?   試想一下,如果js不是單執行緒的,同時有兩個方法作用dom,一個刪除,一個修改,那麼這時候瀏覽器該聽誰的?這就是js被設計成單執行緒的原因。   2.js為什麼需要非同步?

js中值的基本型別與引用型別以及物件引用物件的淺拷貝與深拷貝

js有兩種型別的值:棧:原始資料型別(undefinen,null,boolead,number,string)堆:引用資料型別(物件,函式和陣列)兩種型別的區別是:儲存位置不同,原始資料型別直接儲存在棧(stack)中的簡單資料段,佔據空間小,大小固定,屬於被頻繁使用的資料,所以放入棧中儲存;引用資料型別儲