關於C++的智能指針

  一句話歸納:當類中有指針成員時,可使用智能指針實現對象共享;智能指針經過引用計數實現,即對指向同一對象的指針計數;智能指針的使用能夠方便/安全地控制對象的生命週期,對指針進行自動銷燬。ios

  當類中有指針成員時,通常有兩種方式來管理指針成員:一是採用值型的方式管理,每一個類對象都保留一份指針指向的對象的拷貝;另外一種更優雅的方式是使用智能指針,從而實現指針指向的對象的共享程序員

  智能指針(smart pointer)的一種通用實現技術是使用引用計數(reference count)。智能指針類將一個計數器與類指向的對象相關聯,引用計數跟蹤該類有多少個對象的指針指向同一對象。編程

  智能指針和普通指針的區別在於智能指針其實是對普通指針加了一層封裝機制,這樣的一層封裝機制的目的是爲了使得智能指針能夠方便的管理一個對象的生命期安全

  在C++中,咱們知道,若是使用普通指針來建立一個指向某個對象的指針,那麼在使用完這個對象以後咱們須要本身刪除它,例如:函數

    ObjectType* temp_ptr = new ObjectType();測試

    temp_ptr->foo();this

    delete temp_ptr;spa

  ①若是忘記在調用完以後刪除temp_ptr,那麼會形成一個懸掛指針(dangling pointer),也就是說這個指針如今指向的內存區域其內容程序員沒法把握和控制,也可能很是容易形成內存泄漏。設計

  ②但是事實上,不止是「忘記」,在上述的這一段程序中,若是foo()在運行時拋出異常,那麼temp_ptr所指向的對象仍然不會被安全刪除。指針

  在這個時候,智能指針的出現實際上就是爲了能夠方便的控制對象的生命期,在智能指針中,一個對象何時和在什麼條件下要被析構或者是刪除是受智能指針自己決定的,用戶並不須要管理。

  有四種類型的智能指針引用自https://www.zhihu.com/question/20368881/answer/14918675

1) scoped_ptr: 這是比較簡單的一種智能指針,正如其名字所述,scoped_ptr所指向的對象在做用域以外會自動獲得析構,一個例子是:https://www.boost.org/doc/libs/1_50_0/libs/smart_ptr/scoped_ptr.htm
此外,scoped_ptr是non-copyable的,也就是說你不能去嘗試複製一個scoped_ptr的內容到另一個scoped_ptr中,這也是爲了防止錯誤的屢次析構同一個指針所指向的對象。

2) shared_ptr: 不少人理解的智能指針實際上是shared_ptr這個範疇。shared_ptr中實現的本質是引用計數(reference counting),也就是說shared_ptr是支持複製的,複製一個shared_ptr的本質是對這個智能指針的引用次數加1,而當這個智能指針的引用次數下降到0的時候,該對象自動被析構。須要特別指出的是,若是shared_ptr所表徵的引用關係中出現一個環,那麼環上所述對象的引用次數都確定不可能減爲0那麼也就不會被刪除,爲了解決這個問題引入了weak_ptr。

3) weak_ptr: 對weak_ptr起的做用,不少人有本身不一樣的理解,我理解的weak_ptr和shared_ptr的最大區別在於weak_ptr在指向一個對象的時候不會增長其引用計數,所以你能夠用weak_ptr去指向一個對象而且在weak_ptr仍然指向這個對象的時候析構它,此時你再訪問weak_ptr的時候,weak_ptr其實返回的會是一個空的shared_ptr。實際上,一般shared_ptr內部實現的時候維護的就不是一個引用計數,而是兩個引用計數,一個表示strong reference,也就是用shared_ptr進行復制的時候進行的計數,一個是weak reference,也就是用weak_ptr進行復制的時候的計數。weak_ptr自己並不會增長strong reference的值,而strong reference下降到0,對象被自動析構。爲何要採起weak_ptr來解決剛纔所述的環狀引用的問題呢?須要注意的是環狀引用的本質矛盾是不能經過任何程序設計語言的方式來打破的,爲了解決環狀引用,第一步首先得打破環,也就是得告訴C++,這個環上哪個引用是最弱的,是能夠被打破的,所以在一個環上只要把原來的某一個shared_ptr改爲weak_ptr,實質上這個環就能夠被打破了,原有的環狀引用帶來的沒法析構的問題也就隨之獲得瞭解決。

4) intrusive_ptr: 簡單的說,intrusive_ptr和shared_ptr的區別在於intrusive_ptr要求其所指向的對象自己實現一個引用計數機制,也就是說當對象自己包含一個reference counter的時候,可使用intrusive_ptr。在實際使用中我幾乎歷來沒有見到過intrusive_ptr...

傳統指針的問題:
  當你在堆上建立了一個對象時,系統就把這個對象的生命期徹底交給了你,當用完以後,系統並不會回收資源,而是須要你來釋放它。
那麼,既然要負責對象的釋放問題,就要知道何時釋放和在哪裏釋放。若是你沒有處理好這兩個問題,就會形成內存泄漏或程序崩潰的問題

//1 內存泄漏 str1所指的資源沒有被釋放
{
    string* str1 = new string("hello");
    string* str2 = new string("world");
}

//2 多重釋放,引發程序崩潰
{
    string* str1 = new string("hello");
    delete str1;
    //...
    delete str1;//引發程序崩潰
}

  在上述的例子中,指針的釋放是在同一做用域中進行的,你們在編程中也能很容易避免上述代碼中的問題。
  可是,對於一個大型項目,在某一處建立的對象,可能並不會在對應做用域中釋放,而是等到某些事件發生,異常處理等狀況下才會去銷燬對象,對於這樣的問題每每是很難排查出來的。
  因此,有必要引用一種機制來負責指針的自動銷燬。而不是由程序員自己去手動銷燬。智能指針偏偏就是這樣的一種機制。

引用計數的智能指針原理:(來自:知乎:小豆君的乾貨鋪)

1. 當從堆上申請了一個資源時,咱們就建立一個智能指針對象,使它指向這個資源,同時,在堆上申請一個用於計數的資源,讓後來全部的指向該資源的對象都共享這個計數資源,這樣,引用計數的個數就只有一份。
2. 當將ptr1對象賦值給對象ptr2時,其共享的引用計數變爲2。
3. 刪除ptr2對象時,其對應的引用計數減爲1。
4. 刪除ptr1對象時,引用計數變爲0,則釋放資源。

下面這段代碼來自:知乎:小豆君的乾貨鋪

#pragma once

template<class T>
class SharedPointer
{
public:
    //默認構造函數,內部指針,未指向任何資源,引用計數爲0,由於它未與任何資源綁定
    SharedPointer() :m_refCount(nullptr), m_pointer(nullptr){}
    
    //構造函數,初始化時,指向一個已經分配好的資源
    SharedPointer(T* adoptTarget) :m_refCount(nullptr), m_pointer(adoptTarget)
    {
        addReference();
    }
    
    //構造函數,使用其它對象建立新對象
    SharedPointer(const SharedPointer<T>& copy)
        :m_refCount(copy.m_refCount), m_pointer(copy.m_pointer)
    {
        addReference();
    }
    
    //析構函數,引用計數遞減,當爲0時,釋放資源
    virtual ~SharedPointer()
    {
        removeReference();
    }
    
    //賦值操做
    //當左值被賦值時,代表它再也不指向所指的資源,故引用計數減一
    //以後,它指向了新的資源,因此對應這個資源的引用計數加一
    SharedPointer<T>& operator=(const SharedPointer<T>& that)
    {
        if (this != &that)
        {
            removeReference();
            this->m_pointer = that.m_pointer;
            this->m_refCount = that.m_refCount;
            addReference();
        }
        return *this;
    }
    
    //判斷是否指向同一個資源
    bool operator==(const SharedPointer<T>& other)
    {
        return m_pointer == other.m_pointer;
    }
    bool operator!=(const SharedPointer<T>& other)
    {
        return !operator==(other);
    }
    
    //指針解引用
    T& operator*() const
    {
        return *m_pointer;
    }
    //調用所知對象的公共成員
    T* operator->() const
    {
        return m_pointer;
    }
    
    //獲取引用計數個數
    int GetReferenceCount() const
    {
        if (m_refCount)
        {
            return *m_refCount;
        } 
        else
        {
            return -1;
        }
    }
    
protected:
    //當爲nullpter時,建立引用計數資源,並初始化爲1
    //不然,引用計數加1。
    void addReference()
    {
        if (m_refCount)
        {
            (*m_refCount)++;
        }
        else
        {
            m_refCount = new int(0);
            *m_refCount = 1;
        }
    }
    
    //引用計數減一,當變爲0時,釋放全部資源
    void removeReference()
    {
        if (m_refCount)
        {
            (*m_refCount)--;
            if (*m_refCount == 0)
            {
                delete m_refCount;
                delete m_pointer;
                m_refCount = 0;
                m_pointer = 0;
            }
        }
    }
    
private:
    int * m_refCount;
    T   * m_pointer;
};

 對上述代碼進行測試:

#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include "SharedPointer.h"

using namespace std;
class MyClass
{
public:
    ~MyClass()
    {
        cout << "釋放MyClass(" << _id << ")\n";
    }
    
    MyClass(int i) :_id(i)
    {
        
    }
    
    void Print() const
    {
        cout << "MyClass(" << _id << ")" << endl;
    }
private:
    int _id;
};

int main()
{
    {
        MyClass* px = new MyClass(1);
        
        SharedPointer<MyClass> ap(px);
        SharedPointer<MyClass> bp = ap;
        SharedPointer<MyClass> cp;
        
        cout << "ap的引用計數(2): "
             << ap.GetReferenceCount() << endl;
        
        cout << "將ap賦值給cp\n";
        cp = ap;
        
        cout << "ap的引用計數(3): "
             << ap.GetReferenceCount() << endl;
        
        
        MyClass* qx = new MyClass(5);
        SharedPointer<MyClass> dp(qx);
        ap = dp;
        
        cout << "ap的引用計數(2): "
             << ap.GetReferenceCount() << endl;
        
        cout << "dp的引用計數(2): "
             << dp.GetReferenceCount() << endl;
        
        //"像指針同樣使用智能指針"
        dp->Print();
        (*cp).Print();
    }
    
    cin.get();
}

參考:

https://baike.baidu.com/item/%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88/10784135?fr=aladdin

https://zhuanlan.zhihu.com/p/63488452

https://www.zhihu.com/question/20368881/answer/14918675

相關文章
相關標籤/搜索