C++智能指針詳解

1.智能指針的產生背景及發展歷史:

    在C++中動態內存的管理由一對運算符來管理:new,爲動態內存分配空間並返回一個指向該對象的指針;delete,接受一個動態對象的指針,並銷燬該對象,釋放與之相關聯的內存。有時我們忘記釋放內存,造成內存泄漏;有時在有指針引用內存的情況下就釋放了它,在這種情況下會產生引用非法內存的指針。
RAII(一種解決問題的思想)
     RAII(Resource Acquisition Is Initialization),也稱爲爲“資源獲取就是初始化”,是C++語言的一種管理資源、避免泄漏的慣用法。C++標準保證任何情況下,已構造的對象最終會銷燬,即它的析構函數最終會被調用。簡單的說,RAII 的做法是使用一個對象,在其構造時獲取資源,在對象生命期控制對資源的訪問使之始終保持有效,最後在對象析構的時候釋放資源。通過應用RAII思想,就產生了智能指針:
    智能紙指針的行爲類似常規指針;重要的是它負責自動釋放所指的對象。

發展歷程:
這裏寫圖片描述

2.模擬實現智能指針

1>模擬實現auto_ptr
template <class T>
class AutoPtr  
{
public:
    AutoPtr(T *ptr)
        :_ptr(ptr)
    {
    }
    ~AutoPtr()
    {
        if (_ptr != NULL)
        {
            delete _ptr;
            _ptr = NULL;
        }
    }
    AutoPtr(AutoPtr <T> & ap)
        :_ptr(ap._ptr)
    {
        ap._ptr = NULL; 
 //只能有一個對象的成員指針指向同一塊區域,前一個對象的成員指針置爲空指針
    }

    T& operator  //注意返回值問題不能返回一個臨時變量,臨時變量具有常性,會導致不能通過解引用更改值
    {
        return *(this->_ptr);
    }

    AutoPtr& operator=(AutoPtr &ap)
    {
        //防止自賦值
        if (this != &ap)
        {
            delete _ptr;
            this->_ptr = ap._ptr;
            ap._ptr = NULL;
        }
        return *this;
    }

    T*  operator->()
    {
        //this->_ptr->;
        return _ptr;
    }
private:
    T *_ptr;
};

class AA
{
public:
    int _a;
    int _b;
};


//測試用例

void TestAutoPtr()
{
    AutoPtr<int> ap1(new int);
    *ap1 = 10;
    AutoPtr<int> ap2(ap1); 
    //*ap1 = 20;  
    //這句代碼會導致程序崩潰,因爲當有兩個對象的成員指針指向同一塊區域
    //時,前一個對象的成員指針會被置成空指針 
    AutoPtr<int> ap3(new int(20));
    ap2 = ap3;
    AutoPtr<AA> ap4(new AA);
    ap4->_a = 10;
    ap4->_b = 20;
    ap2 = ap3;
}
2>模擬實現scoped_ptr(和unique_ptr功能類似)
template<class T>
class ScopedPtr   
{
public:
    ScopedPtr(T *ptr)
        :_ptr(ptr)
    {
    }

    ~ScopedPtr()
    {
        if (_ptr)
        {
            delete _ptr;
            _ptr = NULL;
        }
    }

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

    T* operator ->()
    {
        return _ptr;
    }
protected:
    //構造和拷貝構造只聲明,不定義
    ScopedPtr& operator=(const ScopedPtr & sp);
    ScopedPtr(const ScopedPtr & sp);
private:
    T* _ptr;
};

class AA
{
public:
    int _a;
    int _b;
};

//測試用例
void TestScopedPtr()
{

    ScopedPtr<int> ap1(new int);
    *ap1 = 10;
    ScopedPtr<int> ap3(new int(10));
//  ap3 = ap1;
//  ScopedPtr<int> ap4(ap1);
//上述兩句代碼編譯不通過,因爲ScopedPtr的構造和拷貝構造只聲明,沒定義,
//且爲了防止別人進行修改定義爲保護或者私有成員
    ScopedPtr<AA> ap2(new AA);
    ap2->_a = 10;
    ap2->_b = 20;
}
3>shared_ptr的模擬實現(仿函數版)(實際實現要複雜)

仿函數:

     仿函數(functor),就是使一個類的使用看上去象一個函數。其實現就是類中實現一個operator(),這個類就有了類似函數的行爲,就是一個仿函數類了。
    沒有使用仿函數,就不能實現SharedPtr的多種功能,只使用delete刪除的話,如果是new[]的空間,就會造成內存泄漏。

這裏寫圖片描述

template<class T>
struct DeleteArray
{
    void operator()(T *ptr)
    {
        delete[] ptr;
    }

};

template<class T>
struct Delete
{
    void operator()(T *ptr)
    {
        delete ptr;
    }

};

struct Fclose
{
    void operator()(FILE * ptr)
    {
        fclose(ptr);
    }
};

template<class T,class Del>
class SharedPtr  
{
public:
    SharedPtr(T *ptr)
        :_ptr(ptr)
        , _reference_count(new int (1))
    {
    }
    ~SharedPtr()
    {
        Clear();
    }
    void Clear()
    {
        if (--(*_reference_count) == 0)
        {
             _del(_ptr);
            delete _reference_count;
            _reference_count = NULL;
        }

    }
    SharedPtr( SharedPtr<T,Del> & sp)
    {
        _ptr = sp._ptr;
        _reference_count = sp._reference_count;
        (*_reference_count)++;
    }

    //傳統寫法
    //SharedPtr& operator =(const SharedPtr<T,Del> & sp)
    //{
    //  if (this != &sp)
    //  {
    //      Clear();
    //      _ptr = sp._ptr;
    //      _reference_count = sp._reference_count;
    //      (*_reference_count)++;
    //  }
    //  return *this;
    //}

    //現代寫法
    SharedPtr& operator =(SharedPtr<T,Del> sp)
    {
        swap(_ptr, sp._ptr);
        swap(_reference_count, sp._reference_count);
        return *this;
    }
    T& operator *() 
    {
        return *(this->_ptr);
    }

    T*  operator->()
    {
        return _ptr;
    }
private:
    T *_ptr;
    Del _del;
    int *_reference_count;
};


//測試用例
void TestSharedPtr()
{
    SharedPtr<int,Delete<int>> ap3(new int(10));
    SharedPtr<int,Delete<int>> ap4(ap3);
    SharedPtr<int,DeleteArray<int>> ap5(new int[10]);
    SharedPtr<FILE, Fclose>ap6(fopen("text.txt", "w"));
}
4>weak_ptr產生原因:
     shared_ptr雖然功能強大,但是存在一些問題,比如說循環引用問題。
struct ListNode
{
    shared_ptr<ListNode> _prev;
    //shared_ptr爲庫文件中實現的,只需包memory即可使用  
    shared_ptr<ListNode> _next;
    int data;
    ListNode(int x)
    {
        data = x;
        _prev = NULL;
        _next = NULL;
    }
    ~ListNode()
    {
        cout << "~ListNode()" << endl;
    }
};
int main()  
{  
    shared_ptr<ListNode> cur(new ListNode(1));
    shared_ptr<ListNode> next(new  ListNode(2));
    cur->_next = next;
    next->_prev = cur;
    return 0;        
}  

循環引用:
這裏寫圖片描述
爲了解決shared_ptr循環引用所帶來的問題,就有了weak_ptr:

  weak_ptr是一種不控制所指對象生存週期的智能指針,它指向一個shared_ptr所管理的對象。將一個weak_ptr綁定到shared_ptr不會改變shared_ptr的引用計數。一旦最後一個指向對象的shared_ptr被銷燬,對象就會被釋放。weak_ptr的名字抓住了這種智能指針“弱”共享對象的特點。

3.智能指針的總結

這裏寫圖片描述

發佈了49 篇原創文章 · 獲贊 61 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章