smart_ptr之shared_ptr

 

一、介绍引用计数指针

       几乎所有稍微复杂点的程序都需要某种形式的引用计数智能指针。这些智能指针让我们不再需要为了控制被两个或多个对象共享的对象的生存期而编写复杂的逻辑<这种逻辑通常被叫做“计数管理”>。当引用计数降为零,没有对象再需要这个共享的对象,这个对象就自动被销毁了。引用计数智能指针可以分为插入式(intrusive)和非插入式(non-intrusive)两类。前者要求它所管理的类提供明确的函数或数据成员用于管理引用计数。这意味着在类的设计时就必须预见到它将与一个插入式的引用计数智能指针一起工作,或者重新设计它。非插入式的引用计数智能指针对它所管理的类没有任何要求。引用计数智能指针拥有与它所存指针有关的内存的所有权。没有智能指针的帮助,对象的共享会存在问题,必须有人负负责删除共享的内存。谁负责?什么时候删除?没有智能指针,你必须在资源管理之外增加额外的生存期管理这意味着在各个拥有者之间存在更强的依赖关系。换言之,没有了重用性并增加了复杂性

被管理的类可能拥有一些特性使得它更应该与引用计数智能指针一起使用。例如:

它的复制操作很昂贵,或者它所代表的有些东西必须被多个实例共享,这些特性都值得去共享所有权。

还有一种情形是共享的资源没有一个明确的拥有者。使用引用计数智能指针可以在需要访问共享资源的对象之间共享资源的所有权。

引用计数智能指针还让你可以把对象指针存入标准库的容器中而不会有泄漏的风险<前面介绍的指针都不存在的特性>,特别是在面对异常或要从容器中删除元素的时候。如果你把指针放入容器,你就可以获得多态的好处,可以提高性能(如果复制的代价很高的话),还可以通过把相同的对象放入多个辅助容器来进行特定的查找。

在你决定使用引用计数智能指针后,你应该选择插入式的还是非插入式的?非插入式智能指针几乎总是更好的选择,由于它们的通用性、不需要修改已有代码,以及灵活性。你可以对你不能或不想修改的类使用非插入式的引用计数智能指针。而把一个类修改为使用插入式引用计数智能指针的常见方法是从一个引用计数基类派生。这种修改可能比你想象的更昂贵。至少,它增加了相关性并降低了重用性。它还增加了对象的大小,这在一些特定环境中可能会限制其可用性

 

shared_ptr

boost库smart_ptr中最重要,最精华,最值得关注,最.........................,太多的“最”都不为过,因为这个智能指针对象,基本上已经包含了前面和后面即将继续介绍的指针的特性:对资源的生命周期进行管理,能够实现相关的copy construct和assign 操作符,而不用操心资源的管理,使用“非插入式”的引用计数技术<有部分指针是属于插入式的,这就需要被管理的类实现相关的引用计数的管理,这个主要是对遗留代码的兼容>,对于以前和以后的代码都具有很好的兼容性,因此这个指针是一个比较重要的指针。被收入了TR1

 

shared_ptr 可以从一个裸指针、另一个shared_ptr、一个std::auto_ptr、或者一个boost::weak_ptr<后面介绍,类似于一个观察者>构造。还可以传递第二个参数给shared_ptr的构造函数,它被称为删除器(deleter)<这是其他智能指针所不具有的特性,提供了灵活性和方便性>。删除器稍后会被调用,来处理共享资源的释放。这对于管理那些不是用new分配也不是用delete释放的资源时非常有用(稍后将看到创建客户化删除器的例子)。shared_ptr被创建后,它就可象普通指针一样使用了,除了一点,它不能被显式地删除。

 

部分成员函数介绍:

 shared_ptr(): px(0), pn() // never throws

默认构造函数

 

template <class Y> explicit shared_ptr(Y* p): px( p ), pn( p ); Y must be complete

这个构造函数获得给定指针p的所有权<不进行所有权的转移哦,是共享所有权>。参数p 必须是指向Y 的有效指针<完整类型>。构造后引用计数设为1<这个非常重要,因为不能把这个裸指针直接给其他的shared_ptr ptr1,或者ptr1会认为自己是第一个拥有该指针,因此计数为1,后面进行资源释放就会出现异常,因为把握原则,后面的赋值等均需要时智能指针对智能指针的>。唯一从这个构造函数抛出的异常是std::bad_alloc (仅在一种很罕见的情况下发生,即不能获得引用计数器所需的自由空间没法分配)。


template <class Y,class D> shared_ptr(Y* p,D d);//Requirements: D's copy constructor must not throw

这个构造函数带有两个参数。第一个是shared_ptr将要获得所有权的那个资源,第二个是shared_ptr被销毁时负责释放资源的一个对象,被保存的资源将以d(p)的形式传给那个对象。因此p的值是否有效取决于d。如果引用计数器不能分配成功,shared_ptr抛出一个类型为std::bad_alloc的异常。

 

template<class Y, class D, class A> shared_ptr( Y * p, D d, A a ): px( p ), pn( p, d, a )

这个构造函数带有三个参数。前两个和上面的相同,第三个参数主要是提供对引用计数进行内存分配的定制分配器。


shared_ptr(const shared_ptr & r);

r中保存的资源被新构造的shared_ptr所共享,引用计数加一。这个构造函数不会抛出异常。


template <class Y> explicit  shared_ptr(const weak_ptr<Y>& r);

从一个 weak_ptr(构造shared_ptr。这使得weak_ptr的使用具有线程安全性,因为指向weak_ptr参数的共享资源的引用计数将会自增(weak_ptr不影响共享资源的引用计数)。如果weak_ptr为空 (r.use_count()==0),shared_ptr 抛出一个类型为bad_weak_ptr的异常


template <class Y> explicit shared_ptr(std::auto_ptr<Y>& r);

这个构造函数从一个auto_ptr获取r中保存的指针的所有权,方法是保存指针的一份拷贝并对auto_ptr调用release。构造后的引用计数为1。而r当然就变为空的。如果引用计数器不能分配成功,则抛出std::bad_alloc

需要说明一下:在现在发行的boost库中,shared_ptr中是没有析构函数的,可能会有人会感觉到比较奇怪,那么当引用计数为0的时候,谁去保证释放资源?以前的智能的智能指针都是自己释放资源,自己保管使用权限,但是shared_ptr因为把引用计数交给了专门的类来进行处理,故资源的释放也是由这个引用计数的类来保证!shared_ptr不进行管理!

具体的涉及类有如下几个:<后面会专门分析>

 boost::detail::shared_count pn-------------->sp_counted_base * pi_------------------->virtual void dispose() = 0

由最后的这个纯虚函数保证的,中间的这个sp_counted_base作为一个基类,派生了三个子类,根据不同的删除器定制了自己释放资源的方式。

在很大程度上可以从“proxy”的设计模式去考虑,shared_ptr代理了pointer,然后关键的部分实现代理给了shared_count ,后者使用sp_counted_base 


shared_ptr& operator=(const shared_ptr& r);

赋值操作共享r中的资源,并停止对原有资源的共享。赋值操作不会抛出异常。

void reset();

reset函数用于停止对保存指针的所有权的共享。共享资源的引用计数减一。源代码中有多个重载函数,根据需要选择相关的接口操作即可!


T& operator*() const;

这个操作符返回对已存指针所指向的对象的一个引用。如果指针为空,调用operator* 会导致未定义行为<使用之前需要进行检查>。这个操作符不会抛出异常。


T* operator->() const;

这个操作符返回保存的指针。这个操作符与operator*一起使得智能指针看起来象普通指针,如果指针为空,这也会导致为定义的行为,需要进行安全检查。这个操作符不会抛出异常。


T* get() const;

get函数是当保存的指针有可能为空时(这时 operator*operator-> 都会导致未定义行为)获取它的最好办法。注意,你也可以使用隐式布尔类型转换来测试shared_ptr 是否包含有效指针。这个函数不会抛出异常。

bool unique() const;

这个函数在shared_ptr是它所保存指针的唯一拥有者时返回true ;否则返回 falseunique 不会抛出异常。


long use_count() const;

use_count 函数返回指针的引用计数。它在调试的时候特别有用,因为它可以在程序执行的关键点获得引用计数的快照。小心地使用它,因为在某些可能的shared_ptr实现中,计算引用计数可能是昂贵的,甚至是不行的。这个函数不会抛出异常。由sp_counted_base返回的,看源码就可以了解 。


operator unspecified_bool_type() const;

这是个到unspecified-bool-type类型的隐式转换函数,它可以在Boolean上下文中测试一个智能指针。如果shared_ptr保存着一个有效的指针,返回值为True;否则为false。注意,转换函数返回的类型是不确定的。把返回类型当成bool用会导致一些荒谬的操作,所以典型的实现采用了safe bool idiom,它很好地确保了只有可适用Boolean测试可以使用。这个函数不会抛出异常。<在一篇博文中已经介绍了相关技术O(∩_∩)O>


void swap(shared_ptr<T>& b);

这可以很方便地交换两个shared_ptrswap 函数交换保存的指针(以及它们的引用计数)。这个函数不会抛出异常。

 

普通函数

template<class T, class U> shared_ptr<T> shared_static_cast(shared_ptr<U> const & r)
{
    return shared_ptr<T>(r, boost::detail::static_cast_tag());
}

// 实现指针之间的静态类型转化,类似于对内置类型进行static_cast的转换,因为对于智能指针后期的操作(资源)都只能是对shared_ptr的操作,如果直接对裸指针进行static_cast的转换,然后再赋值给指针指针,就会出现管理上的混乱,出现资源的多次释放等问题。因此提供了如下的一系列问题,特别是在进行“多态实现的时候”一定要记得使用下面的转化,否则会出现异常。

template<class T, class U> shared_ptr<T> shared_dynamic_cast(shared_ptr<U> const & r)
{
    return shared_ptr<T>(r, boost::detail::dynamic_cast_tag());
}

template<class T, class U> shared_ptr<T> shared_polymorphic_cast(shared_ptr<U> const & r)
{
    return shared_ptr<T>(r, boost::detail::polymorphic_cast_tag());
}

template<class T, class U> shared_ptr<T> shared_polymorphic_downcast(shared_ptr<U> const & r)
{
    BOOST_ASSERT(dynamic_cast<T *>(r.get()) == r.get());
    return shared_static_cast<T>(r);
}

 

基本用法举例说明

(1)shared_ptr具有共享资源所有权的意义,并且可以在合适的时候进行资源的释放。

#include "boost/shared_ptr.hpp"
#include <cassert>
class A 
{  
    boost::shared_ptr<int> no_;
public:  
    A(boost::shared_ptr<int> no) : no_(no) 

    {}  
    void value(int i) 

   {    *no_=i;  }
};


class B 
{  
    boost::shared_ptr<int> no_;
public:  
    B(boost::shared_ptr<int> no) : no_(no) 
    {}  
    int value() const 
    {    
        return *no_;  
    }
};


int main() 
{    
    boost::shared_ptr<int> temp(new int(14));    // 资源,引用计数为1
    A a(temp);                                                      // 共享,引用计数为2
    B b(temp);                                                     // 共享,引用计数为3
    a.value(28);                                                   // 对共享资源做改变
    assert(b.value()==28);                                  // 共享对象都发生改变
}

AB都保存了一个 shared_ptr<int>. 在创建 AB的实例时,shared_ptr temp 被传送到它们的构造函数。这意味着共有三个 shared_ptra, b, 和 temp,它们都引向同一个int实例。如果我们用指针来实现对一个的共享,A B 必须能够在某个时间指出这个int要被删除。在这个例子中,直到main的结束,引用计数为3,当所有 shared_ptr离开了作用域,计数将达到0,而最后一个智能指针将负责删除共享的 int.


(2)shared_ptr与标准容器一起使用。

以前介绍的智能指针,因为所有权的转移或者是一旦拥有所有权就不能进行转移的特性,导致都不能很好的和STL或者是boost库自己的容器很好的使用,但是shared_ptr自己实现了construct和assign的操作,因此可以很好的很STL容器一起使用,同时还具有相关的多态特性。<解决了资源管理和共享使用的烦恼>

一个入门级的例子:shared_ptr与stl容器和algorithm一起使用

class CBase
{

protected:
    virtual ~CBase(){}
public:
    virtual void print() = 0;
    virtual int val() = 0;
};


class COne : public CBase
{
    int value;
public:
    COne( int v ):value(v)
    {}
    void print()
    {
        std::cout << " Cone: " << value << std::endl;
    }
    int val()
    {
        return value;
    }
};


class CTwo : public CBase
{
    int value;
public:
    CTwo( int v ):value(v)
    {}
    void print()
    {
        std::cout << " CTwo: " << value << std::endl;
    }


    int val()
    {
        return value;
    }
};


class CThree : public CBase
{
    int value;
public:
    CThree( int v ):value(v)
    {}
    void print()
    {
        std::cout << " CThree: " << value << std::endl;
    }
    int val()
    {
        return value;
    }
};

// 函数对象
struct equal_value
{
    int value;
    explicit equal_value( int v ):value(v)
    {}
    bool operator()( const boost::shared_ptr<CBase> &rhs )
    {
        if ( rhs->val() == value )
            return true;
        return false;
    }
};

int main() 
{    
    typedef std::vector<boost::shared_ptr<CBase> > vecType;
    vecType vec;
    vec.push_back( boost::shared_ptr<COne>( new COne(1)) );
    vec.push_back( boost::shared_ptr<CTwo>( new CTwo(2)) );
    vec.push_back( boost::shared_ptr<CThree>( new CThree(3)) );

    vecType::const_iterator it = vec.begin();
    for( ; vec.end() != it; ++it )
        (*it)->print();

    std::cout << " algorithm " << std::endl;
    it = std::find_if( vec.begin(), vec.end(), equal_value(2) );
    if ( it != vec.end() )
        (*it)->print();

    return 0;
}

输出结果如下:


可以看到,上面的例子只是简单的展示了相关的部分功能,完全具有指针的特性,支持多态同时也可以和算法一起使用,并且资源的生存期不用你去管理,避免了部分资源管理和共享的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值