zoukankan      html  css  js  c++  java
  • 【转发】boost::shared_ptr 分析与实现

     

    boost::shared_ptr 分析与实现

    http://blog.chinaunix.net/u/14337/showart_299314.html

    /************************************************************
    * file: shared_ptr
    *
    * desc: 本文将对boost::shared_ptr作一详细的介绍。 本文介绍的不是用法,而是
    * 智能指针的原理,结构以及boost对其的实现. 最后还会给出一个简化了的实现。
    *
    * author: whiteear
    * date: 2007-05-10
    * copyright: 任意发布
    ***********************************************************/


    shared_ptr

    1 boost 介绍

    关于boost的介绍, 网上一大堆。 google一下就行。 作为初学者, 也没有能力去全面介绍它。

    2 shared_ptr 介绍

    在boost库中, 智能指针并不只shared_ptr一个。同族但有不同的功能目标的还有如下5个:
    scoped_ptr
    scoped_array
    shared_ptr
    shared_array
    weak_ptr

    前面两个, 与标准C++中的智能指针auto_ptr功能基本类似, 不过它不传递所有权, 不可复制。
    从其名称就可以看出, 其主要目标就是在小范围, 小作用域中使用,以减少显式的delete, new配对操作,
    提高代码的安全性。scoped_ptr是针对指针的版本, 而scoped_array同是专门针对数组的。(记住, 删除
    一个指针使用delete p; 而删除一个数组使用delete[] p);

    后面两个是本文的重点,它们在所有对象中, 共享所指向实体的所有权。 即只要是指向同一个实体
    对象的shared_ptr对象, 都有权操作这个对象,并根据自己产生新的对象,并把所有权共享给新的对象。即它
    是满足STL对对象的基本要求可复制,可赋值的。可以与所有的STL容器,算法结合使用。顾名思义, shared_ptr
    是针对任意类型的指针的, 而shared_array则是专门针对任意类型的数组的。

    最后一个weak_ptr。

    3 shared_ptr 分析

    3.1 一些思考

    C++对内存数据的直接可操作性以及指针的使用,给编程带来了极大的灵活,
    但同时也还来了很大的问题。 程序在运行时,如果出现segment fault,
    大部分原因在于非法访问了内存。
    或者访问了一个越界的地址,或者访问了一个已经失效的指针。我们可以随时、随地的NEW一个对象, 并把它赋值给某个指针。
    在以后随时要吧通过此指针访问这个对象。
    但问题时NEW出来的对象在内存的堆空间中,并不是函数调用栈中,也就是说一个New出来的对象,从生命期上来说,
    是全局的, 只要不Delete, 它就一直存在。 相对的实际的内存是有限的,
    我们不能只索取而不释放, 否则终有一刻会内存耗尽,New不出新对象。

    问题1: 什么时候释放?
    New很简单, Delete也很简单。 一个New配对一个Delete也很简单。但不简单的就是什么时候New, 什么时候Delete。New还好办些,
    有需求的时候就可以New, 那什么时候一个New出来的对象不再有用呢?什么时候它该被Delete呢?
    对于这个问题, Java采用的时GC的机制,由虚拟机通过一定的算法实现。
    但Java的GC机制被人诟病也不是一天两天了。 它太慢,太吃内存了。
    C++因为各种各样的原因而没有采用GC, 把这个问题直接丢给了程序员。

    问题2: 资源申请即初始化
    在C++之父Bjarne Stroustrup老大的巨著《The C++ Programming Language》第14章,
    第4节资源管理介绍了“资源申请即初始化”。 利用局部对象管理资源的技术通常被说成是“资源申请即初始化”。
    这种技术依赖于构造函数和析构函数的特性, 以及它们与异常机制的关系。

    我们知道, 一个对象在超出它的作用域时, 会自动被析构。 那么我们就可以设想, 可不可以将New
    出来的对象,托管给另一个对象,让它自动的在超出作用域时, 去释放New出来的对象。 这样我们就可以
    高枕无忧的去New, 释放的时机就转嫁给托管对象(事实上转嫁给了编译器,因为它知道什么时候这个对
    象超出作用域)。

    问题3: 托管的资源
    当一个New出来的资源被托管之后, 我们所有针对此资源的操作就应该通过这个代理商来进行。当然我
    们也可以绕过这个代理商直接与资源对话。这时候我们是不是又回归到了原始的那种状态, 在得到一部分
    灵活性, 直接性的同时, 也失去了代理商所具有的管理功能。

    事实上, 考查一下C++的指针就会知道, 指针本身也是一个代理商。 只不过它是效率最高的代理商(也许
    是资源厂商自己设置的一级代码吧,)。 通过指针我们可以间接的访问它所指代的实体对象。 通过*(dereference)
    操作可以得到实体, 通过->可以直接访问实体的内容。 抽象这些特征(类似于STL中Iterator的概念),
    我们的新的代理也应该具有这样的功能。

    问题4: 释放时机的确定
    通过回答上面3个问题。我们似乎已经解决了所有的问题。
    我们得到了一个代理, 将我们的资源托管给它。通过代理我们以熟悉的方式访问,
    操作资源。在合适的时候代理自动帮助我们释放掉不再使用的资源。 一切都很美好!!!

    类似下面的代码应该可以很好的工作:
    work()
    {
    resource r = new resource();
    proxy p(r);
    // use p to do something
    }

    当work被执行完成时, 资源r自动被p释放掉。 我们得到的益处好象并不多,
    只是少写了几个delete语句。
    考虑一下,r是在堆栈中的,所以它的生命期是比较长的,
    我们如果想在work之外使用r呢? 如果同时有多个p的复本时呢?
    在那一个p被析构时真正的释放这个唯一的资源呢?

    考虑一下现实生活中, 一个资源厂商通常会有多家代理,
    任意一家代理都可以处理这个资源。 考虑一下,
    如果没有任何一个代理愿意去代理这个资源厂商的资源,是不是表明这个资源并不被市场所接收,即它是无用的,应该被淘汰的。
    这个时候厂商就应该去相关部门申请耍破产了。

    我们的代理也应该这样,它自动的统计有多少个对当前资源的代理,
    当没有代理去托管这个资源时, 就应该释放这个资源(delete)。

    问题5: 同步
    现在多线程编程已经非常普遍, 大家都喜欢一心二用, 三用, 甚至N用。
    如果采用上面4个问题的回答来解决资源管理的问题。那么对第4个问题的回答就还缺少点。

    当我们使用一个记数器去记录当前对此资源的代理有多少个时,
    这个记数器本身是不是也成为一个关键性的资源!!!
    对记数器的操作特别是有新代理加入或某个代理析构时, 都要对记数进行修改。
    随着多线程的加入,就必须要保证记数器值在所有的代理中都是一样的, 是同步的。 否则后果真是难以想象。

    因此在我们的代理设计中, 必须要对记数器部分作线程同步。


    3.2 boost::shared_ptr的解决方案


    上面我们分析了shared_ptr问题的产生,以及可能的实现方式和应该注意的问题。 下面我们来看看boost::shared_ptr是如何实现上面的思想的。

    3.2.1 文件结构
    定义boost::shared_ptr主要涉及到以下文件

    shared_ptr.hpp
      detail/shared_count.hpp
      detail/sp_counted_base.hpp
      detail/sp_counted_base_pt.hpp
      detail/sp_counted_base_win32.hpp
      detail/sp_counted_base_nt.hpp
      detail/sp_counted_base_pt.hpp
      detail/sp_counted_base_gcc_x86.hpp
      ...   
      detail/sp_counted_base_impl.hpp

    涉及的类主要有以下几个:
    shared_ptr
    shared_count
    sp_counted_base
    sp_counted_base_impl

    其中
    shared_ptr定义在shared_ptr.hpp中
    share_count定义在shared_count.hpp中
    sp_counted_base定义在sp_counted_base_XXX.hpp中。
    XXX指代针对特定平台的实现。
    sp_counted_base_impl定义在sp_counted_base_impl.hpp中

    3.2.2 类功能
    sp_counted_base 是问题4中, 记数器的实现。
    针对不同的平台使用了不同的同步机制。 如pt是针对linux, unix平台使用pthread接口进行同步的。
    win32是针对windows平台, 使用InterlockedIncrement, InterlockedDecrement机制。
    gcc_x86是针对AMD64硬件平台的, 内部使用汇编指令实现了atomic_increment,
    atomic_decrement。

    sp_counted_base_impl 是个模板, 它继承自sp_counted_base,
    主要实现了父类中一个纯虚函数dispose。具体的由它来负责在记数值到0(即没有代理时)释放所托管的资源。

    shared_count是个类模板。它存在的意义在于和代理类shared_ptr同生共死,
    在构造函数中生成记数器,在代理的传递过程中驱动记数器增减。
    shared_ptr是个类模板。 它是托管资源的代理类, 所有对资源的操作,
    访问都通过它来完成。

    下图展示了它们之间的相互关系









    figure01 shared_ptr.jpeg





    3.2.3 类详细介绍

    shared_ptr 类模板。 它是我们直接使用的代理类。
    两个属性
    pn : shared_count 记数器类。 shared_ptr完全操作pn的生命期,
    在构造时构造它,在析构时自动析构它。
    这些都是利用构造与析构函数的特性自动完成的。

    template<class Y>
    explicit shared_ptr( Y * p ): px( p ), pn( p ) // Y must be
    complete
    {
    detail::sp_enable_shared_from_this( pn, p, p );
    }

    px : T* 它是代理的资源的指针。 所有针对资源的操作*,
    ->都直接使用它来完成。

    reference operator* () const // never throws
    {
    BOOST_ASSERT(px != 0);
    return *px;
    }

    T * operator-> () const // never throws
    {
    BOOST_ASSERT(px != 0);
    return px;
    }


    两个方法:
    operator=完成代理的赋值传递。
    通过这样,就可以有多个代理同时托管同一个资源。在众多的代理中, 它们共享所托管资源的操作权。

    shared_ptr & operator=(shared_ptr const & r) // never throws
    {
    px = r.px;
    pn = r.pn; // shared_count::op= doesn't throw
    return *this;
    }

    T* get()资源原始位置的获取。通过这个方法,我们可以直接访问到资源,
    并可以对它进行操作。

    T * get() const // never throws
    {
    return px;
    }

    user_count返回当前资源的代理个数,即有多少个对些资源的引用。

    long use_count() const // never throws
    {
    return pn.use_count();
    }


    为了方便操作,并完全模拟原生指针的行为,boost::shared_ptr还定义了大量的其它操作函数。


    shared_count类 记数器的包装。
    一个属性:
    pi : sp_counted_base *它指向真正的记数器。

    构造函数:
    template<class Y> explicit shared_count( Y * p ): pi_( 0 )
    #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
    , id_(shared_count_id)
    #endif
    {
    #ifndef BOOST_NO_EXCEPTIONS

    try
    {
    pi_ = new sp_counted_impl_p<Y>( p );
    }
    catch(...)
    {
    boost::checked_delete( p );
    throw;
    }

    #else

    pi_ = new sp_counted_impl_p<Y>( p );

    if( pi_ == 0 )
    {
    boost::checked_delete( p );
    boost::throw_exception( std::bad_alloc() );
    }

    #endif
    }


    当使用一个资源指针来构造一个shared_count时,它知道针对此资源要生成一个代理。
    所以生成一个记数器pi。如果在构造记数器的过程中出现任何异常行为,即记数器资源的初始化未成功完成时, 就释放掉资源。
    (这就是资源申请即初始化, 对于一个资源管理类来说,要不所有资源申请成功, 要不构造失败)

    operator=赋值函数。
    在shared_ptr被赋值的时候,会调用它。经过复制后一个shared_ptr变成两个, 所以要对记数器进行增加。
    同时如果被赋值的代理原有托管的资源将被释放。

    shared_count & operator= (shared_count const & r) // nothrow
    {
    sp_counted_base * tmp = r.pi_;

    if( tmp != pi_ )
    {
    if( tmp != 0 ) tmp->add_ref_copy();
    if( pi_ != 0 ) pi_->release();
    pi_ = tmp;
    }

    return *this;
    }

    析构函数析构函数在shared_ptr超出作用域被析构时自动调用。
    每析构一个shared_ptr,则代理数就少一个, 所以调用记数器的release函数,
    减少记数值。

    ~shared_count() // nothrow
    {
    if( pi_ != 0 ) pi_->release();
    #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
    id_ = 0;
    #endif
    }

    sp_counted_base 记数器类。
    这是一个普通类,没有被模板化。它定义了记数器公用的所有操作, 并实现了同步。

    两个属性
    use_count记录当前的代理数目。
    mutex互斥锁(在不同平台的实现中,是不同的类型。以linux为例,
    是pthread_mutex_t)


    构造函数初始化互斥锁和记数
    sp_counted_base(): use_count_( 1 ), weak_count_( 1 )
    {
    #if defined(__hpux) && defined(_DECTHREADS_)
    pthread_mutex_init( &m_, pthread_mutexattr_default );
    #else
    pthread_mutex_init( &m_, 0 );
    #endif
    }

    析构函数释放互斥锁
    virtual ~sp_counted_base() // nothrow
    {
    pthread_mutex_destroy( &m_ );
    }

    同步增加记数值,即根据原有的复制构造出或通过赋值产生新的代理时记数加1
    void add_ref_copy()
    {
    pthread_mutex_lock( &m_ );
    ++use_count_;
    pthread_mutex_unlock( &m_ );
    }

    释放函数,在代理超界被析构时使用。 它首先减少引用记数,
    然后查看记数值,如果为0, 则调用dispose释放资源。并销毁掉本身
    void release() // nothrow
    {
    pthread_mutex_lock( &m_ );
    long new_use_count = --use_count_;
    pthread_mutex_unlock( &m_ );

    if( new_use_count == 0 )
    {
    dispose();
    weak_release();
    }
    }

    释放托管资源的函数。 为纯虚函数, 要求在子类中实现。
    只所以不同这个类中实现,是由于要托管的类型是未知的,如果要实现则要记录这个资源,则此类不得不模板化。
    将此功能分离到子类中的好处就非常明显。子类可以直接利用父类的功能,只要模板化一下,实现一个函数就行。
    这样会大大加快编译速度。

    virtual void dispose() = 0; // nothrow

    销毁函数。 在shared_count中的pi即记数器是New出来的。
    所以在适当的时候要销毁掉。 而这个时机就是资源被释放的时候。
    即记数器完成了对资源的管理, 同时完成了对自身的管理。

    // destroy() is called when weak_count_ drops to zero.
    virtual void destroy() // nothrow
    {
    delete this;
    }


    sp_counted_base_impl 类模板。
    一个属性

    px : T*记录各种类型的资源。

    实现了父类的dispose函数。 实现对资源的释放。
    virtual void dispose() // nothrow
    {
    #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
    boost::sp_scalar_destructor_hook( px_, sizeof(X), this );
    #endif
    boost::checked_delete( px_ );
    }

    3.2.4 工作流程

    1 用户申请一个资源p
    p = new Something();

    2 用户将资源p托管给shared_ptr;
    shared_ptr(p);

    2.1 shared_ptr 构造函数调用,构造出记数对象shared_count
    shared_ptr() : px(p), pn(p)

    2.1.1 shared_count 构造函数调用,构造出户数器对象
    pi = new sp_counted_base_impl(p);

    3 用户使用shared_ptr对象sp, 就象直接使用资源一样
    *sp;
    sp->func();
    ..

    4 如果用户要传递sp
    shared_ptr<T> spt = sp;

    4.1 调用shared_ptr.operator=
    4.1.1 调用shared_count.operator=
    4.1.1.1 调用sp_counted_base复制构造
    4.1.2 调用sp_counted_base.add_ref_copy, 增加引用记数

    5 如果sp超出了它本身的作用域, 则调用析构函数
    5.1 调用shared_count.~shared_count 析构记数
    5.1.1 调用sp_counted_base.release, 减少引用记数
    如果引用记数已经减少到0,则
    调用sp_counted_base_impl.dispose, 销毁资源。
    调用sp_counted_base.destroy, 销毁自身。

    4 实践

    上面是对boost::shared_ptr的分析。 整个过程就是对smart
    pointer这种观点的思考过程。 基本上是想到什么记录什么。 光说不练不行,
    根据对boost::shared_ptr的分析, 本人仿作了一个shared_ptr,
    可以看成是对boost::shared_ptr的一个简略版。 有没有用先不说, 反正都是写程序。

    全部原代码见附件shared_ptr.tar.gz

    5 参考
    [1] boost www.boost.org
    [2] shared_ptr性能分析
    http://blog.csdn.net/ralph623/archive/2005/08/18/458414.aspx
    [3] smart pointer in boost
    http://www.ddj.com/dept/cpp/184401507?pgno=1
    [4] the new C++: smart pointer http://www.ddj.com/dept/cpp/184403837
    [5] C++中的智能指针
    http://www.stlchina.org/twiki/bin/view.pl/Main/BoostProgrammSmartPoint




  • 相关阅读:
    POJ 2236 Wireless Network(并查集)
    POJ 2010 Moo University
    POJ 3614 Sunscreen(贪心,区间单点匹配)
    POJ 2184 Cow Exhibition(背包)
    POJ 1631 Bridging signals(LIS的等价表述)
    POJ 3181 Dollar Dayz(递推,两个long long)
    POJ 3046 Ant Counting(递推,和号优化)
    POJ 3280 Cheapest Palindrome(区间dp)
    POJ 3616 Milking Time(dp)
    POJ 2385 Apple Catching(01背包)
  • 原文地址:https://www.cnblogs.com/zhyg6516/p/1990587.html
Copyright © 2011-2022 走看看