其主要的类关系如下所示(省略相关的类模板参数):

图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

C++ 智能指针(shared_ptr/weak_ptr)原理分析的更多相关文章

  1. 深入学习c++--智能指针(二) weak_ptr(打破shared_ptr循环引用)

    1. 几种智能指针 1. auto_ptr: c++11中推荐不使用他(放弃) 2. shared_ptr: 拥有共享对象所有权语义的智能指针 3. unique_ptr: 拥有独有对象所有权语义的智 ...

  2. STL源码剖析-智能指针shared_ptr源码

    目录一. 引言二. 代码实现 2.1 模拟实现shared_ptr2.2 测试用例三. 潜在问题分析 你可能还需要了解模拟实现C++标准库中的auto_ptr一. 引言与auto_ptr大同小异,sh ...

  3. [6] 智能指针boost::weak_ptr

    [1]boost::weak_ptr简介 boost::weak_ptr属于boost库,定义在namespace boost中,包含头文件 #include<boost/weak_ptr.hp ...

  4. c/c++ 智能指针 shared_ptr 使用

    智能指针 shared_ptr 使用 上一篇智能指针是啥玩意,介绍了什么是智能指针. 这一篇简单说说如何使用智能指针. 一,智能指针分3类:今天只唠唠shared_ptr shared_ptr uni ...

  5. C++智能指针shared_ptr

    shared_ptr 这里有一个你在标准库中找不到的—引用数智能指针.大部分人都应当有过使用智能指针的经历,并且已经有很多关于引用数的文章.最重要的一个细节是引用数是如何被执行的—插入,意思是说你将引 ...

  6. c/c++ 智能指针 shared_ptr 和 new结合使用

    智能指针 shared_ptr 和 new结合使用 用make_shared函数初始化shared_ptr是最推荐的,但有的时候还是需要用new关键字来初始化shared_ptr. 一,先来个表格,唠 ...

  7. 智能指针shared_ptr新特性shared_from_this及weak_ptr

    enable_shared_from_this是一个模板类,定义于头文件<memory>,其原型为: template< class T > class enable_shar ...

  8. C++ 智能指针 shared_ptr 分析

    引文: C++对指针的管理提供了两种解决问题的思路: 1.不允许多个对象管理一个指针 2.允许多个对象管理一个指针,但仅当管理这个指针的最后一个对象析构时才调用delete ps:这两种思路的共同点就 ...

  9. 智能指针shared_ptr的用法

    为了解决C++内存泄漏的问题,C++11引入了智能指针(Smart Pointer). 智能指针的原理是,接受一个申请好的内存地址,构造一个保存在栈上的智能指针对象,当程序退出栈的作用域范围后,由于栈 ...

  10. 智能指针 shared_ptr 解析

    近期正在进行<Effective C++>的第二遍阅读,书里面多个条款涉及到了shared_ptr智能指针,介绍的太分散,学习起来麻烦.写篇blog整理一下. LinJM   @HQU s ...

随机推荐

  1. Dockerfile之CMD与Entrypoint使用要点

    CMD与ENTRYPOINT都可以代表容器的启动命令,单丛语义上来理解,CMD是一个命令或者口令,而ENTRYPOINT则是一个入口(相当于容器启动时的入口),那么其实就可以理解为每当我们开启一个容器 ...

  2. 一些 tips

    在本博客中,一般写题解的题都是我认为比较有价值的题,然而我还做过一些有一定价值,但并没有达到值得写一篇题解的程度,故将这些题目总结出的套路用一句话概括在这里: 当然如果看到我太久不更请在评论区里催我一 ...

  3. R语言与医学统计图形-【26】ggplot2主题函数

    ggplot2绘图系统--主题函数 1. theme函数 theme_*系列函数提供了9种不同的风格. theme_grey/gray/bw/linedraw/light/minimal/classi ...

  4. exit(0) exit(1) return() 3个的区别

    exit(0):正常运行程序并退出程序: exit(1):非正常运行导致退出程序: return():返回函数,若在主函数中,则会退出函数并返回一值. 详细说: 1. return返回函数值,是关键字 ...

  5. EXCEL-如何在excel中对图片进行批量排版

    新建EXCEL->导入图片->如果每张图高度为33个单元格,共计10张图,那么将最后边的那张图(即正对着你的那一张)剪切粘贴到33*9行第一个单元格处->按F5定位"对象& ...

  6. centos安装后的个人工具

    1.安装vim工具 yum -y install vim 安装完成后在家目录下新建一个.vimrc的配置文件.辅助vim软件功能. set number " 显示行号 set cursorl ...

  7. 在 vscode.dev 中直接运行 Python !纯浏览器环境,无后端!

    其实有挺长一段时间没有写自己的 VS Code 插件了! 还是要感谢我们 DevDiv 组的 Flexible Friday 活动,让我可以在工作日研究自己感兴趣的项目. Flexible Frida ...

  8. 数仓day03-----日志预处理

    1. 为什么要构建一个地理位置维表(字典) 在埋点日志中,有用户的地理位置信息,但是原始数据形式是GPS坐标,而GPS坐标在后续(地理位置维度分析)的分析中不好使用.gps坐标的匹配,不应该做这种精确 ...

  9. linux下的C++多线程

    原文链接:http://blog.csdn.net/lee1054908698/article/details/54633056 本随笔作为多线程笔记使用,内容完全照搬原博 多线程是多任务处理的一种特 ...

  10. 【Python】【Module】random

    mport random print random.random() print random.randint(1,2) print random.randrange(1,10) 随机数 import ...