C++ 智能指針(shared_ptr/weak_ptr)原理分析


其主要的類關系如下所示(省略相關的類模板參數):

 

圖1

 

從上面的類圖可以清楚的看出shared_ptr內部含有一個指向被管理對象(managed object)T的指針以及一個__shared_count對象,__shared_count對象包含一個指向管理對象(manager object)的基類指針,管理對象(manager object)由具有原子屬性的use_count和weak_count、指向被管理對象(managed object)T的指針、以及用來銷毀被管理對象的deleter組成,以下均將用new創建后托管給shared_ptr等智能指針管理的對象叫做被管理對象(managed object);shared_ptr等智能指針內部創建的用來維護被管理對象生命周期的實例叫做管理對象(manager object):

 

圖2

 

weak_ptr內部組成與shared_ptr類似,內部同樣含有一個指向被管理對象T的指針以及一個__weak_count對象:

 

圖3

 

從圖2和圖3對比可以看出,shared_ptr與weak_ptr的差異主要是由__shared_ptr與__weak_ptr體現出來的,而__shared_ptr與__weak_ptr的差異則主要是由__shared_count與__weak_count體現出來。

通過shared_ptr的構造函數,可以發現,在創建一個shared_ptr的時候需要一個new 操作符返回被管理對象的地址來初始化shared_ptr, shared_ptr在內部會構建一個_shared_count對象,由_shared_count對象的構造函數可知,創建shared_ptr的時候也動態的創建了一個管理對象_Sp_counted_base_impl:

 

    template<typename _Tp1> explicit __shared_ptr(_Tp1* __p)
    : _M_ptr(__p), _M_refcount(__p) {
        __glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)
        typedef int _IsComplete[sizeof(_Tp1)];
        __enable_shared_from_this_helper(_M_refcount, __p, __p);
    }
     
    template<typename _Ptr>
    __shared_count(_Ptr __p) : _M_pi(0)
    {
        __try
       {
          typedef typename std::tr1::remove_pointer<_Ptr>::type _Tp;
          _M_pi = new _Sp_counted_base_impl<_Ptr, _Sp_deleter<_Tp>, _Lp>(__p, _Sp_deleter<_Tp>());
        }
        __catch(...)
        {
            delete __p;
        __throw_exception_again;
        }
    }

 

shared_ptr內部包含一個指向被管理對象的指針_M_ptr, _Sp_counted_base_impl內部也含有一個指向被管理對象的指針_M_ptr, 它們是不是重復多余了呢?

實際上不多余,它們有各自的功能。這首先要從shared_ptr的拷貝構造或者賦值構造說起,當一個shared_ptr對象sp2是由sp1拷貝構造或者賦值構造得來的時候,實際上構造完成后sp1內部的__shared_count對象包含的指向管理對象的指針與sp2內部的__shared_count對象包含的指向管理對象的指針是相等的,也就是說當多個shared_ptr對象來管理同一個對象時,它們共同使用同一個動態分配的管理對象。這可以從下面的__share_ptr的構造函數和__shared_count的構造函數清楚的看出。

 

    template<typename _Tp1>
     __shared_ptr(const __shared_ptr<_Tp1, _Lp>& __r)
     : _M_ptr(__r._M_ptr), _M_refcount(__r._M_refcount) // never throws
    {__glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)}
     
     
    __shared_count&
    operator=(const __shared_count& __r) // nothrow
    {
        _Sp_counted_base<_Lp>* __tmp = __r._M_pi;
        if (__tmp != _M_pi)
        {
            if (__tmp != 0)
                __tmp->_M_add_ref_copy();
        if (_M_pi != 0)
            _M_pi->_M_release();
        
            _M_pi = __tmp;
        }
    }

 

上面說說當多個shared_ptr對象來管理同一個對象時,它們共同使用同一個動態分配的管理對象,為什么上面給出的_shared_count的構造函數中出現了__tmp != _M_pi的情形呢?這在sp2未初始化時(_M_pi為0,_r._M_pi非0)便是這樣的情形。

更一般的,也可以考慮這樣的情形:shared_ptr實例sp1開始指向類A的實例對象a1, 另外一個shared_ptr實例sp2指向類A的實例對象a2(a1 != a2),當把sp2賦值給sp1時便會出現上面的情形。假設初始時有且僅有一個sp1指向a1, 有且僅有一個sp2指向a2; 則賦值結束時sp1與sp2均指向a2, 沒有指針指向a1, sp1指向的a1以及其對應的管理對象均應該被析構。這在上面的代碼中我們可以很清楚的看到:因為__tmp != _M_pi,  __tmp->_M_add_ref_copy()將會增加a2的use_count的引用計數;由於a1內部的_M_pi != 0, 將會調用其_M_release()函數:

 

 

    //************_Sp_counted_base*****************//
    void
    _M_add_ref_copy()
    { __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }
     
     
    //************_Sp_counted_base*****************//
    void
    _M_release() // nothrow
    {
        // Be race-detector-friendly.  For more info see bits/c++config.
        _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);
        if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1)
        {
                _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
            _M_dispose();
            // There must be a memory barrier between dispose() and destroy()
            // to ensure that the effects of dispose() are observed in the
            // thread that runs destroy().
            // See http://gcc.gnu.org/ml/libstdc++/2005-11/msg00136.html
            if (_Mutex_base<_Lp>::_S_need_barriers)
            {
                __atomic_thread_fence (__ATOMIC_ACQ_REL);
            }
     
                // Be race-detector-friendly.  For more info see bits/c++config.
                _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
            if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1)
                {
            _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
                _M_destroy();
                 }
        }
    }
     
    //************_Sp_counted_base*****************//
    // Called when _M_use_count drops to zero, to release the resources
    // managed by *this.
    virtual void
    _M_dispose() = 0; // nothrow
     
    // Called when _M_weak_count drops to zero.
    virtual void
    _M_destroy() // nothrow
    { delete this; }
     
    //************_Sp_counted_base_impl*************//
    virtual void
    _M_dispose() // nothrow
    { _M_del(_M_ptr); }

 

_M_release()函數首先對a1的use_count減去1,並對比減操作之前的值,如果減之前是1,說明減后是0,a1沒有任何shared_ptr指針指向它了,應該將a1對象銷毀,於是調用_M_dispose()函數銷毀a1; 同時對a1的weak_count減去1,也對比減操作之前的值,如果減之前是1,說明減后是0,a1沒有weak_ptr指向它了,應該將管理對象銷毀,於是調用_M_destroy()銷毀了管理對象。這就可以解答為什么圖2所示中shared_ptr內部含有兩個指向被管理對象的指針了:__shared_ptr直接包含的裸指針是為了實現一般指針的->,*等操作,通過__shared_count間接包含的指針是為了管理對象的生命周期,回收相關資源。

換句話說,__shared_count內部的use_count主要用來標記被管理對象的生命周期,weak_count主要用來標記管理對象的生命周期。

當一個shared_ptr超出作用域被銷毀時,它會調用其_share_count的_M_release()對use_count和weak_count進行自減並判斷是否需要釋放管理對象和被管理對象,這是RAII原理的核心體現:

 

    ~__shared_count() // nothrow
     {
         if (_M_pi != 0)
          _M_pi->_M_release();
     }

 

對於weak_ptr, 其對應的__weak_count的拷貝構造函數如下:

 

    //************_Sp_counted_base*****************//
     void
     _M_weak_add_ref() // nothrow
    { __gnu_cxx::__atomic_add_dispatch(&_M_weak_count, 1); }
     
    //************_Sp_counted_base*****************//
    void
    _M_weak_release() // nothrow
    {
        // Be race-detector-friendly. For more info see bits/c++config.
        _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
        if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1)
        {
            _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
        if (_Mutex_base<_Lp>::_S_need_barriers)
        {
            // See _M_release(),
            // destroy() must observe results of dispose()
                __atomic_thread_fence (__ATOMIC_ACQ_REL);
        }
        _M_destroy();
        }
    }
     
    __weak_count<_Lp>&
    operator=(const __shared_count<_Lp>& __r) // nothrow
    {
        _Sp_counted_base<_Lp>* __tmp = __r._M_pi;
        if (__tmp != 0)
            __tmp->_M_weak_add_ref();
      
        if (_M_pi != 0)
            _M_pi->_M_weak_release();
      
        _M_pi = __tmp;  
        
        return *this;
    }
          
    __weak_count<_Lp>&
    operator=(const __weak_count<_Lp>& __r) // nothrow
    {
        _Sp_counted_base<_Lp>* __tmp = __r._M_pi;
        if (__tmp != 0)
            __tmp->_M_weak_add_ref();
        if (_M_pi != 0)
            _M_pi->_M_weak_release();
        _M_pi = __tmp;
        
        return *this;
    }
     
    __weak_count<_Lp>&
    operator=(const __shared_count<_Lp>& __r) // nothrow
    {
        _Sp_counted_base<_Lp>* __tmp = __r._M_pi;
        if (__tmp != 0)
          __tmp->_M_weak_add_ref();
        if (_M_pi != 0)
          _M_pi->_M_weak_release();
        _M_pi = __tmp;  
        return *this;
    }
     
    ~__weak_count() // nothrow
    {
        if (_M_pi != 0)
            _M_pi->_M_weak_release();
    }

 

從上面可以看出:

    __weak_count相關的賦值拷貝以及析構函數均只會影響到weak_count的值,對use_count沒有影響;當weak_count為0時,釋放管理對象。也就是說__weak_ptr不影響被管理對象的生命周期。同時由於__weak_ptr沒有像__shared_ptr那樣實現*,->等常見指針相關操作符,__weak_ptr不能直接操作被管理對象;
    __weak_count自身間的賦值以及__shared_count對__weak_count的賦值時,它們都具有同樣的指向管理對象的指針;也就是說當多個__weak_ptr和__shared_ptr指向同一個被管理對象時,它們共享同一個管理對象,這就保證了可以通過__weak_ptr可以判斷__shared_ptr指向的被管理對象是否存在以及獲取到被管理對象的指針。

 

__shared_ptr與__weak_ptr在管理同一對象時,它們間的關系如下圖4所示:

 

圖4

 

由於weak_ptr不能直接操作被管理對象但其仍然持有指向被管理對象的指針(用來初始化內部的__weak_count對象),weak_ptr與被管理對象用虛線聯接。

_weak_ptr有幾個重要的成員函數:通過expired()方法來判斷對象是否過期(已經被釋放);通過use_count()方法返回目前有多少個__shared_ptr對象指向被管理對象;通過lock()方法返回一個指向被管理對象的__shared_ptr指針,調用者可以通過這個__shared_ptr指針來操縱被管理對象而不用擔心資源泄漏;

 

    /*************_weak_ptr*************************/
    long
    use_count() const // never throws
    { return _M_refcount._M_get_use_count(); }
     
    bool
    expired() const // never throws
    { return _M_refcount._M_get_use_count() == 0; }
     
    __shared_ptr<_Tp, _Lp>
    lock() const // never throws
    {
    #ifdef __GTHREADS
           // Optimization: avoid throw overhead.
        if (expired())
                  return __shared_ptr<element_type, _Lp>();
     
        __try
        {
                return __shared_ptr<element_type, _Lp>(*this);
        }
        __catch(const bad_weak_ptr&)
        {
            // Q: How can we get here?
           // A: Another thread may have invalidated r after the
           //    use_count test above.
           return __shared_ptr<element_type, _Lp>();
         }
        
    #else
        // Optimization: avoid try/catch overhead when single threaded.
        return expired() ? __shared_ptr<element_type, _Lp>()
                         : __shared_ptr<element_type, _Lp>(*this);
     
    #endif
    } // XXX MT
     

 

當然shared_ptr也不是萬能的,使用的時候也要注意到它給程序員挖的一個大坑:shared_ptr能夠管理對象的生命周期,負責對象資源釋放,其前提條件是所有shared_ptr共用同一個管理對象。如果多個shared_ptr使用多個管理對象來管理同一個被管理對象,這些管理對象在use_count為0時均會釋放被管理對象,將會造成多個管理對象多次釋放被管理對象,造成twice delete的堆錯誤。下面的例子在單獨使用裸指針的時候沒有問題,采用shared_ptr將會出現twice delete的問題:

 

 

    class Thing {
    public:
        void foo();
        void defrangulate();
    };
    void transmogrify(Thing *);
    int main()
    {
        Thing * t1 = new Thing;
        t1->foo();
        ...
        delete t1; // done with the object
    }
    ...
    void Thing::foo()
    {
        // we need to transmogrify this object
        transmogrify(this);
    }
    void transmogrify(Thing * ptr)
    {
        ptr->defrangulate();
        /* etc. */
    }
    //***** Use shared_ptr***************************//
    class Thing {
    public:
        void foo();
        void defrangulate();
    };
    void transmogrify(shared_ptr<Thing>);
    int main()
    {
        shared_ptr<Thing> t1(new Thing); // create manager object A for the Thing
        t1->foo();
        ...
        // Thing is supposed to get deleted when t1 goes out of scope
    }
    void Thing::foo()
    {
        // we need to transmogrify this object
        shared_ptr<Thing> sp_for_this(this); //  create manager object B for the Thing
        transmogrify(sp_for_this);
        
        // Thing is supposed to get deleted when sp_for_this and other shared_ptr goes out of scope
    }
    void transmogrify(shared_ptr<Thing> ptr)
    {
        ptr->defrangulate();
        /* etc. */
    }

 

上面注釋處分別創建了兩個shared_ptr指針t1,sp_for_this, 它們各自有自己的管理對象,但被管理的堆內存卻是一樣的,這就導致在t1和sp_for_this析構時,它們各自的管理對象均會析構被管理對象,造成twice delete。

 

怎樣解決上面這一廣泛存在問題:當一個對象M創建后,如果一個函數f(另一個類的成員函數或是其它自由函數)的形參為M類型的智能指針,如何在對象M內部將對象M的指針作為實參傳遞給該函數f ? C++引入了enable_shared_from_this利用weak_ptr的特性解決了這一問題。其基本思想是通過M繼承模板類enable_shared_from_this,這樣對象M內部將會有一個__weak_ptr指針_M_weak_this,在第一次創建指向M的shared_ptr Pt時,通過模板特化,將會初始化_M_weak_this;這樣M內部也會產生一個指向自身的weak_ptr,並且該weak_ptr內部的管理對象與Pt的管理對象是相同的(這可以從weak_ptr內部的_M_assign函數看出)。

 

 

    // Friend of enable_shared_from_this.
    template<typename _Tp1, typename _Tp2>
    void __enable_shared_from_this_helper(const __shared_count<>&, const enable_shared_from_this<_Tp1>*, const _Tp2*);
     
     
    template<typename _Tp1>
    explicit __shared_ptr(_Tp1* __p)
    : _M_ptr(__p), _M_refcount(__p)
    {
        __glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>) typedef int _IsComplete[sizeof(_Tp1)];
        __enable_shared_from_this_helper(_M_refcount, __p, __p);
     
    }
     
    template<typename _Tp>
    class enable_shared_from_this
    {
    protected:
        enable_shared_from_this() { }
          
        enable_shared_from_this(const enable_shared_from_this&) { }
     
        enable_shared_from_this&
        operator=(const enable_shared_from_this&)
        { return *this; }
     
        ~enable_shared_from_this() { }
     
    public:
        shared_ptr<_Tp>
        shared_from_this()
        { return shared_ptr<_Tp>(this->_M_weak_this); }
     
        shared_ptr<const _Tp>
        shared_from_this() const
        { return shared_ptr<const _Tp>(this->_M_weak_this); }
     
        private:
        template<typename _Tp1>
        void
        _M_weak_assign(_Tp1* __p, const __shared_count<>& __n) const
        { _M_weak_this._M_assign(__p, __n); }
     
        template<typename _Tp1>
        friend void
        __enable_shared_from_this_helper(const __shared_count<>& __pn, const enable_shared_from_this* __pe, const _Tp1* __px)
        {
        if (__pe != 0)
         __pe->_M_weak_assign(const_cast<_Tp1*>(__px), __pn);
        }
        
        mutable weak_ptr<_Tp>  _M_weak_this;
    };
     
    _M_assign(_Tp* __ptr, const __shared_count<_Lp>& __refcount)
    {
        _M_ptr = __ptr;
       _M_refcount = __refcount;
    }

 

 

 

這樣,在M內部,當需要傳遞指向M的智能指針時,可以通過繼承而來的shared_from_this方法獲取到指向M的智能指針而不會發生內存泄漏。上面示例中改寫后的正確代碼為:

 

 

    class Thing : public enable_shared_from_this<Thing> {
    public:
        void foo();
        void defrangulate();
    };
    int main()
    {
        // The following starts a manager object for the Thing and also
        // initializes the weak_ptr member that is now part of the Thing and share same manager object.
        shared_ptr<Thing> t1(new Thing);
        t1->foo();
        ...
    }
    void Thing::foo()
    {
        // get a shared_ptr from the weak_ptr in this object
        shared_ptr<Thing> sp_this = shared_from_this();
        transmogrify(sp_this);
    }
    void transmogrify(shared_ptr<Thing> ptr)
    {
        ptr->defrangulate();
        /* etc. */
    }

 

解決了所有的坑,shared_ptr是不是就十全十美了呢?當然不是,shared_ptr也存在不足:在采用shared_ptr<M> p(new M);形式創建p來管理M時,我們實際發現這中間有兩次的動態內存分配:一次為創建被管理對象M,一次為創建管理對象;而內存分配通常是比較昂貴的操作。

 

如果頻繁的需要創建指向多個不同對象的智能指針,可以采用shared_ptr<M> p(make_shared<M>);的方式,采用這種方式系統將會分配一大塊內存同時存放管理對象和被管理對象,這就避免了上面所說的二次內存分配的問題,同時程序中也不會出現new操作符,符合"no naked new!"的編程倡導。當然這也有缺點,如果所有指向該對象的智能指針都銷毀了,盡管對象的析構函數會被調用,析構被管理對象,但是如果還有weak_ptr指向該塊對象所在的內存,存放管理對象的部分內存仍將不會被釋放,因而導致在所有其他weak_ptr銷毀前整塊內存(盡管被管理對象已經析構了)將不會進入系統的內存池循環使用。

 
————————————————
版權聲明:本文為CSDN博主「ithiker」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/ithiker/article/details/51532484


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM