智能指针之 shared_ptr

本文介绍了C++中的智能指针shared_ptr,强调了其在软件开发中的重要性,特别是作为引用计数型智能指针,可以安全地被复制和共享。shared_ptr解决了auto_ptr和scoped_ptr的问题,通过引用计数实现自动管理动态对象的生命周期。文章讨论了shared_ptr的构造、内部工作原理、操作函数、线程安全性以及在标准容器中的应用。此外,还提及了shared_ptr与其他智能指针的区别及其广泛应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

shared_ptr是一个最像指针的“智能指针”,是boost.smart_ptr库中最有价值、最重要的组成部分,也是最有用的,Boost库的许多组件–甚至还包括其他一些领域的智能指针都使用了shared_ptr。抱歉,我实在想不出什么更恰当的词汇来形容它在软件开发中的重要性。再强调一遍,shared_ptr非常有价值、非常重要、非常有用。——《Boost程序库完全开发指南》

shared_ptr与scoped_ptr一样包装了new操作符在堆上分配的动态对象,但它实现的是引用计数型的智能指针 ,可以被自由地拷贝和赋值,在任意的地方共享它,当没有代码使用(引用计数为0)它时才删除被包装的动态分配的对象。shared_ptr也可以安全地放到标准容器中,并弥补了auto_ptr因为转移语义而不能把指针作为STL容器元素的缺陷。

在C++历史上曾经出现过无数的引用计数型智能指针实现,但没有一个比得上boost::shared_ptr,在过去、现在和将来,它都是最好的。

shared_ptr的思想是,每当另一个智能指针指向该空间时,引用计数++,每当有智能指针更改指向时,引用计数–。这就意味着,有几个智能指针指向该空间,引用计数就是几,当引用计数大于2时,证明有2个以上的智能指针指向该空间,那么析构时,把智能指针赋空,不需释放空间。当且仅当引用计数为1时,即只有一个指针指向该空间,析构时才将指针赋空并释放空间,证明没有任何代码需要管理该空间,就把它释放掉。
引用计数的引入,解决auto_ptr和scoped_ptr不能解决的问题,它使得空间的释放更具有安全性。

shared_ptr的架构由四部分组成。

分别是class shared_ptr:

template<class _Ty>
class shared_ptr
{
    typedef shared_ptr<_Ty> this_type; 
public:
    shared_ptr():px(0){}
    shared_ptr(const shared_ptr<_Ty> &r):px(r.px),pn(r.pn){}
    shared_ptr(_Ty *p):px(p),pn(p){}

    shared_ptr<_Ty>& operator=(const shared_ptr<_Ty> &r)
    {
        if(this != &r)
        {
            this_type(r).swap(*this);
        }
    return *this;
    }
    ~shared_ptr()
public:
    _Ty& operator*()const
        {return *px;}
    _Ty* operator->()const
        {return px;}
public:
    long use_count()const
    {
        return pn.use_count();
    }
public:
    void swap(shared_ptr<_Ty> & b) // never throw
    {
        _Ty * tmp = b.px;
        b.px = px;
        px = tmp;
    }
private:
    _Ty *px;
    shared_count pn;
};

引用计数 class shared_count:

class shared_count
{
public:
    shared_count():pi_(0){}
    template<class _Ty>
    shared_count(_Ty *p):pi_(new sp_counted_impl_xxx<_Ty>(p)){}
    shared_count(const shared_count &r):pi_(r.pi_)
    {
        pi_->add_ref_copy();
    }
    ~shared_count()
    { 
       if(pi_ != 0)
           pi_->release();
    }
 public:
    long use_count()const
    {
        return pi_ ? pi_->use_count() : 0;
    }
private:
    sp_counted_base *pi_;
};

继承基类 class sp_counted_base

class sp_counted_base
{
public:
    sp_counted_base() : use_count_(1){}
     virtual ~sp_counted_base(){}
public:
    virtual void dispose() = 0;
public:
    void add_ref_copy()
        {++use_count_;}
    long use_count()const
    {
        return use_count_;
    }
    void release()
    {
        if(--use_count_ == 0)
        {
            dispose();
            delete this;
        }
    }
private:
    long use_count_;
};

继承子类class sp_counted_impl_xxx:

class sp_counted_impl_xxx : public sp_counted_base
{
public:
    sp_counted_impl_xxx(_Ty *p) : px_(p){}
    ~sp_counted_impl_xxx(){}
public:
    void dispose()
    {
        delete px_;
    }
private:
    _Ty *px_;
};   

shared_ptr的构架如图所示:
这里写图片描述
当将指针传给shared_ptr时,class shared_ptr构造对象,px指向该指针指向的空间,而pn是shared_count类的,所以先构造shared_count的对象,shared_count(Ty *p):pi(new sp_counted_impl_xxx<_Ty>(p)),构造对象时p是子类sp_counted_impl_xxx类型的,所以要先构造父类sp_counted_base的对象。

所以shared_ptr的构造顺序是:
sp_counted_base -> sp_counted_impl_xxx -> shared_count -> shared_ptr

这样设计shared_ptr是有原因的,将各个数据存在不同类中,使得程序更有弹性。
shared_ptr中px指针接受“裸指针”进行管理,而引用计数use_count_存放在父类中,不同的子类都可以继承引用计数的方法,而子类又引入了指针*px_,px_和px是相同的指向,加入这个指针是为了操作方便。
但需要注意的是,这两个指针指向相同,要防止二次释放。px只管接受指针,当引用计数use_count_为1时,px_负责释放空间,同时要将px赋空。(px和px_都属于shared_ptr,所以两者虽指向相同,当count不++)

操作函数
shared_ptr与scoped_ptr同样是管理new动态分配对象的智能指针,因此功能上有很多的相似之处:它们都重载了*和->操作符以模仿原始指针的行为,提供隐式bool类型转换以判断指针的有效性,get()可以得到原始指针,并且没有提供指针算术操作。

  shared_ptr<int>spi(new int)       //一个int的shared_ptr
  assert(spi);                      //在bool语境中隐式转换为bool值
  *spi = 100;                       //使用解引用操作符*
  shared_ptr<string> sps(new string(" "));//一个string的shared_ptr
  assert(sps->size() == 5);         //使用箭头操作符-> 

但shared_ptr的名字表明它与scoped_ptr的主要不同:
shared_ptr是可以被安全共享的——shared_ptr是一个”全功能”的类,有着正常的拷贝,赋值语义,也可以进行shared_ptr间的比较,是最智能的指针!

线程安全性
shared_ptr 本身不是 100% 线程安全的。它的引用计数本身是安全且无锁的,但对象的读写则不是,因为 shared_ptr 有两个数据成员,读写操作不能原子化。根据文档,shared_ptr 的线程安全级别和内建类型、标准库容器、string 一样,即:

  1. 一个 shared_ptr 实体可被多个线程同时读取;
  2. 两个的 shared_ptr 实体可以被两个线程同时写入,“析构”算写操作;
  3. 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁。

应用于标准容器
有两种方式可以将shared_ptr应用于标准容器(或者容器适配器等其他容器)。

一种用法是将容器作为shared_ptr管理的对象,如shared_ptr < list < T > > ,使容器可以被安全地共享,用法与普通指针没有区别。

另一种用法是将shared_ptr作为容器的元素,如
vector < shared_ptr < T > >,因为shared_ptr支持拷贝语义和比较操作,符合标准容器对元素的要求,所以可以实现在容器中安全地容纳元素的指针而不是拷贝。

用法
shared_ptr的智能使得其行为最接近原始指针,因此它比auto_ptr和scoped_ptr的应用更广。几乎是100%可以在任何new出现的地方接受new的动态分配结果,然后被任意的使用,从而完全消灭delete的使用和内存泄漏。

shared_ptr 用法实例一:

shared_ptr<int> sp(new int(10));            //一个指向整数的shared_ptr    
assert(sp.unique());                        //现在shared_ptr是指针的唯一持有者     
shared_ptr<int> sp2 = sp;                   //第二个shared_ptr,拷贝构造函数     
assert(sp == sp2 && sp.use_count() == 2);   //两个shared_ptr相等,指向同一个对象,引用计数为2    
*sp2 = 100;                                 //使用解引用操作符修改被指对象    
assert(*sp == 100);                         //另一个shared_ptr也同时被修改    
sp.reset();                                 //停止shared_ptr的使用    
assert(!sp);                                //sp不再持有任何指针(空指针) 

shared_ptr 用法实例二:

class shared                               //一个拥有shared_ptr的类    
{    
private:        
    shared_ptr<int> p;                     //shared_ptr成员变量    
public:        
    shared(shared_ptr<int> p_):p(p_){}     //构造函数初始化shared_ptr        
    void print()                           //输出shared_ptr的引用计数和指向的值        
    {          
    cout << "count:" << p.use_count()                
    << "v =" <<*p << endl;        
    }    
};    
void print_func(shared_ptr<int> p)         //使用shared_ptr作为函数参数    
{                                                 
    cout << "count:" << p.use_count()      //同样输出shared_ptr的引用计数和指向的值             
        << " v=" <<*p << endl;  }    
int main()    
{        
    shared_ptr<int> p(new int(100));        
    shared s1(p), s2(p);                   //构造两个自定义类         
    s1.print();        
    s2.print();         
    *p = 20;                               //修改shared_ptr所指的值        
    print_func(p);        
    s1.print();    
}

shraed_ptr涉及的问题还远远没有结束,shared_ptr为了封装new还会引入工厂函数系列的问题;sahred_ptr应用于桥接模式和pimpl的问题;以及为了配合shared_ptr的使用 引入weak_ptr(弱指针)的问题;还有intrusive_ptr(侵入式指针)与shared_ptr的比较问题,都是值得大家去深入学习的。
当然,今天就不一一详解了,如果有时间我会继续整理这方面的资料。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值