【C++】 淺析智能指針

引言:
ios

因爲 C++ 語言沒有自動內存回收機制,程序員每次 new 出來的內存都要手動 delete。程序員忘記 delete,流程太複雜,最終致使沒有 delete,異常致使程序過早退出,沒有執行 delete 的狀況並不罕見。程序員



RAII(Resource Acquisition Is Initialization)安全

資源分配即初始化,定義一個類來封裝資源的分配和釋放,在構造函數完成資源的分配和初始化,在析構函數完成資源的清理,能夠保證資源的正確初始化和釋放。數據結構

所謂智能指針就是智能/自動化的管理指針所指向的動態資源的釋放。ide

STL--auto_ptr函數

Boost庫的智能指針(ps:新的C++11標準中已經引入了unique_ptr/shared_ptr/weak_ptr)ui

wKiom1bv4QejFom4AABFmhe2RV0738.png

在這裏,對於aut_optr,scoped_ptr , shared_ptr 進行剖析this

一. aut_optrspa

std::auto_ptr 屬於 STL,固然在 namespace std 中,包含頭文件 #include<memory> 即可以使用。std::auto_ptr 可以方便的管理單個堆內存對象。爲了解決單個對象被重複釋放屢次的狀況,咱們使用的aut_optr的目的就是轉移權限,顧名思義就是說再拷貝構造新的對象時,因爲此時有兩個對象指向同一塊空間,因此將原來的指針賦NULL,而後將這塊空間的全部權交給新的對象指針。線程

對應代碼:<AutoPtr>

#include<iostream>
using namespace std;
 
template<class T>
class AutoPtr
{
public:
    AutoPtr(T* ptr)
    :_ptr(ptr)
    {}
 
    AutoPtr()
    :_ptr(NULL)
    {}
 
    AutoPtr<T>(AutoPtr<T>& ap)   //權限轉移
        : _ptr(ap._ptr)
    {
        ap._ptr = NULL;
    }
 
    AutoPtr<T>& operator=(AutoPtr<T>& ap)
    {
        if (&ap != this)
        {
            delete _ptr;
            _ptr = ap._ptr;
            ap._ptr = NULL;   //權限轉移
        }
        return *this;
    }
 
    ~AutoPtr()
    {
        if (_ptr)
        {
            delete _ptr;
            _ptr = NULL;
        }
    }
 
    T& operator*()
    {
        return *_ptr;
    }
 
private:
    T* _ptr;
};
 
void Test()
{
    AutoPtr<int> ap1(new int(2));
    AutoPtr<int> ap2 = ap1;
    AutoPtr<int> ap3(new int(3));
    ap3 = ap1;
}
 
int main()
{
    Test();
    return 0;
}

wKiom1bv52GRW4a5AAAK7gT15DQ367.png雖然表面上解決了重複釋放的問題,可是卻存在一個很大問題,請看右圖:

(先忽略 s 的存在)

如有s2對象,指向本身的一塊空間,如今要拷貝構造一個s3對象,那麼順應上面的代碼,s2會被置NULL,而這塊空間重歸s3所管理。

若是考慮到這塊空間已經有多個指針指向,即s和s2,那麼再拷貝構造s3時,s2被置空,此時,s變成垂懸指針。這就是一個打的問題所在!


二. scoped_ptr

實用的智能指針,思想就是防拷貝,在大多時候用不到拷貝構造和賦值運算符重載,那麼咱們作的就是寫出構造函數和析構函數,拷貝構造和賦值運算符重載只聲明不定義。這裏有幾點要說明:


<1>在代碼中,將拷貝構造和賦值運算符重載設置成保護或者私有的,爲何?世界之大,總有些人想害朕。。話歸主題,緣由是防止有壞人在外面修改,假若被修改也不會調用修改的,由於沒法訪問獲得。

<2>爲何要聲明拷貝構造和賦值函數,緣由是若是不聲明,編譯器會調用本身默認的構造和賦值函數,那麼防拷貝就無從談起。

代碼以下:<ScopedPtr>

#include<iostream>
using namespace std;
 
template<class T>
class ScopedPtr
{
public:
    ScopedPtr(T* ptr)
        :_ptr(ptr)
    {}
 
    Scoped()
        :_ptr(NULL)
    {}
 
    ~ScopedPtr()
    {
        if (_ptr)
        {
            delete _ptr;
            _ptr = NULL;
        }
    }
 
    T& operator*()
    {
        return *_ptr;
    }
    T* operator->()
    {
        return _ptr;
    }
 
    T* GetPtr()
    {
        return _ptr;
    }
 
protected:
    ScopedPtr<T>(const ScopedPtr<T>& sp);// 只聲明不定義,防拷貝
    ScopedPtr<T>& operator = (const ScopedPtr<T>& sp);
 
private:
    T* _ptr;
};
 
void Test()
{
    ScopedPtr<int> sp1(new int(2));
    ScopedPtr<int> sp2 = sp1;
    ScopedPtr<int> sp3(new int(3));
    sp3 = sp1;
}
 
int main()
{
    Test();
    return 0;
}

auto_ptr和scopedptr的取捨:

boost::scoped_ptr 用於確保動態分配的對象可以被正確地刪除。scoped_ptr 有着與std::auto_ptr相似的特性,而最大的區別在於它不能轉讓全部權而auto_ptr能夠。事實上,scoped_ptr永遠不能被複制或被賦值!scoped_ptr 擁有它所指向的資源的全部權,並永遠不會放棄這個全部權。scoped_ptr的這種特性提高了咱們的代碼的表現,咱們能夠根據須要選擇最合適的智能指針(scoped_ptr 或 auto_ptr)。要決定使用std::auto_ptr仍是boost::scoped_ptr, 就要考慮轉移全部權是否是你想要的智能指針的一個特性。若是不是,就用scoped_ptr. 它是一種輕量級的智能指針;使用它不會使你的程序變大或變慢。它只會讓你的代碼更安全,更好維護。


三. sharede_ptr

在上面咱們看到 boost::scoped_ptr 獨享全部權,不容許賦值、拷貝,boost::shared_ptr 是專門用於共享全部權的,因爲要共享全部權,其在內部使用了引用計數。boost::shared_ptr 也是用於管理單個堆內存對象的。wKioL1bv8Oiw3BzTAAAOMwd2MNI898.png


這個智能指針解決了auto_ptr獨佔的問題,採用引用計數的方法,一旦最後一個這樣的指針被銷燬,也就是一旦某個對象的引用計數變爲0,這個對象會被自動刪除。這在非環形數據結構中防止資源泄露頗有幫助。


用法:刪除共用對象


對應代碼:<SharedPtr>

#include<iostream>
using namespace std;
 
template<class T>
class SharedPtr
{
public:
    SharedPtr(T* ptr)
        :_ptr(ptr)
        , _pCount(new long(1))
    {}
 
    SharedPtr()
        :_ptr(NULL)
        , _pCount(new long(1))
    {}
 
    SharedPtr<T>(const SharedPtr<T>& sp)
        : _ptr(sp._ptr)
        , _pCount(sp._pCount)
    {
        ++(*_pCount);
    }
 
    SharedPtr<T>& operator=(const SharedPtr<T>& sp)
    {
        if (&sp != this)
        {
            if (--(*_pCount) == 0) // 減到0釋放對象一次
            {
                delete _ptr;
                delete _pCount;
            }
            _ptr = sp._ptr;
            _pCount = sp._pCount;
            ++(*_pCount);
        }
        return *this;
    }
 
    ~SharedPtr()
    {
        if (_ptr)
        {
            if (--(*_pCount) == 0)
            {
                delete _ptr;
                delete _pCount;
            }
        }
    }
 
    T& operator*()
    {
        return *_ptr;
    }
    
    T* operator->()
    {
        return _ptr;
    }
 
    long GetCount()
    {
        return *(_pCount);
    }
 
    T* GetPtr()
    {
        return _ptr;
    }
 
private:
    T* _ptr;
    long* _pCount;
};
 
 
void Test()
{
    SharedPtr<int> sp1 = new int(1);
    SharedPtr<int> sp2 = sp1;
    SharedPtr<int> sp3 = new int(2);
    sp3 = sp1;
}
 
 
int main()
{
    Test();

    return 0;
}

可是shared_ptr看起來完美,可是也存在一下問題:

  1.  引用計數更新存在着線程安全

  2.循環引用

  3.定置刪除器


咱們先在這裏討論第二種狀況,即循環引用問題

咱們引出循環引用的場景圖:

wKiom1bzrMzS_i8LAAAIJIxbnM4972.png

此時,兩個節點的引用計數都爲2,由於有兩個指針分別指向a和b,此時就有了一個問題,_next析構時等着_prev析構,_prev要析構,只能b析構;而_prev也在等_next析構,_next要析構,只能a析構。這樣就有個問題相似」你等我,我等你,無休止,永遠不會析構,一直循環「。

要解決這個問題,就又要引出弱指針:weak_ptr


weak_ptr惟一的功能就是解決shared_ptr的循環引用問題,那是怎麼解決的呢?

咱們看代碼:

#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
using namespace boost;

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

     //weak_ptr<ListNode > _prev; // 在此,weak_ptr能夠解決循環引用的問題
     //weak_ptr<ListNode > _next;

    ~ ListNode()
    {
         cout<<"~ListNode()" <<endl;
    }
};

void Test ()
{
     // 循環引用問題
     shared_ptr <ListNode > p1( new ListNode ());
     shared_ptr <ListNode > p2( new ListNode ());

     cout <<"p1->Count:" << p1. use_count()<<endl ;// use_count是庫裏的引用計數
     cout <<"p2->Count:" << p2. use_count()<<endl ;

     // p1節點的_next指向 p2節點
     p1->_next = p2;
     // p2節點的_prev指向 p1節點 
     p2->_prev = p1;

     cout <<"p1->Count:" << p1. use_count ()<<endl ;
     cout <<"p2->Count:" << p2. use_count ()<<endl ;
}

這樣問題就獲得瞭解決。

至此,咱們也知道了,當須要寫一個智能指針時,咱們儘量的去寫scoped_ptr或者shared_ptr,而千萬不要寫auto_ptr,問題上面已經詳細分解。

偏文不到之處,還請評正。

相關文章
相關標籤/搜索