c++ 智能指针- shared_ptr和weak_ptr

本文探讨c++中智能指针shared_ptr和weak_ptr的原理及应用场景,解释如何避免循环引用导致的内存泄露,并讨论enable_shared_from_this的正确使用方式,以增强对c++内存管理的理解。

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

c++中智能指针的作用是非常大的,它不仅能解决很多裸指针带来的内存相关问题,还能用来管理资源(RAII)。

c++中的智能指针有很多种,其中以shared_ptr和weak_ptr最为重要(个人观点),掌握了这两个智能指针的原理,其它的就很好理解了。


shared_ptr是典型的引用计数型智能指针,相信很多c++程序员都写过功能类似的智能指针类。很多时候用这个指针就足够了。

但是有些情况下,shared_ptr可能产生循环引用, 比如Observer模式中,或者父子关系的对象等。为了解决循环引用而造成的内存泄露,引入了weak_ptr。

shared_ptr是strong reference, weak_ptr是weak reference。shared_ptr保证所持有的raw指针是有效的,而weak_ptr则不能保证所持有的指针是否还指向一个有效的对象。

在需要访问所持有的指针的时候,调用weak_ptr的expired或者lock就能判断出是否能得到对应的shared_ptr。


本文的目的不是为了介绍shared_ptr和weak_ptr的使用方法,而是为了介绍它们的实现原理,更主要的是为了说明weak_ptr是如何知道是否还有shared_ptr对象存在的。

还有一些关于它们使用习惯或注意点的说明。


一、shared_ptr

  • ref_count / control block (控制块)

在典型的实现中,std::shared_ptr 只保存两个指针:

  • 指向被管理对象的指针
  • 指向控制块(control block)的指针

控制块是一个动态分配的对象,其中包含:

  • 指向被管理对象的指针或被管理对象本身
  • 删除器
  • 分配器(allocator)
  • 拥有被管理对象的 shared_ptr 的数量
  • 引用被管理对象的 weak_ptr 的数量

通过 std::make_shared 和 std::allocate_shared 创建 shared_ptr 时,控制块将被管理对象本身作为其数据成员;而通过构造函数创建 shared_ptr 时则保存指针。


我们可以通过阅读代码来了解它的具体实现,VS2010的实现是: memory , 这个实现的可读性还是不错的。
(注意:gcc的实现可能和这个不同,即使VS的不同版本之间的实现也可能不同,但原理类似。)
  
class _Ref_count_base { // common code for reference counting
private:
virtual void _Destroy () = 0 ;
virtual void _Delete_this () = 0 ;

long _Uses ;
long _Weaks ;

protected:
_Ref_count_base () : _Uses ( 1 ), _Weaks ( 1 ) { // construct
}
...
};
    
template < class _Ty > class _Ref_count : public _Ref_count_base {
};
template < class _Ty , class _Dx > class _Ref_count_del : public _Ref_count_base {
};
    
template < class _Ty ,
class _Dx ,
class _Alloc >
class _Ref_count_del_alloc
: public _Ref_count_base {
};

template < class _Ty > class _Ptr_base { 
  ...
private:
  
_Ty * _Ptr ;
_Ref_count_base * _Rep ;
};

template < class _Ty class shared_ptr : public _Ptr_base < _Ty {
  
explicit shared_ptr ( _Ux * _Px
{ // construct shared_ptr object that owns _Px
_Resetp ( _Px );
}
    
private:
template < class _Ux >
void _Resetp ( _Ux * _Px )
{ // release, take ownership of _Px
_TRY_BEGIN // allocate control block and reset
_Resetp0 ( _Px , new _Ref_count < _Ux > ( _Px ));
_CATCH_ALL // allocation failed, delete resource
delete _Px ;
_RERAISE ;
_CATCH_END
}
      
public:
template < class _Ux >
void _Resetp0 ( _Ux * _Px , _Ref_count_base * _Rx )
{ // release resource and take ownership of _Px
this -> _Reset0 ( _Px , _Rx );
_Enable_shared(_Px, _Rx);
}
};
      
void _Reset0 ( _Ty * _Other_ptr , _Ref_count_base * _Other_rep )
{ // release resource and take new resource
if ( _Rep != 0 )
_Rep -> _Decref ();
_Rep = _Other_rep ;
_Ptr = _Other_ptr ;
}
};
  
template < class _Ty > class weak_ptr : public _Ptr_base < _Ty > {
...
    
template < class _Ty2 >
weak_ptr ( const shared_ptr < _Ty2 >& _Other ,
typename enable_if < is_convertible < _Ty2 * , _Ty *>:: value ,
void *>:: type * = 0 )
{ // construct weak_ptr object for resource owned by _Other
this -> _Resetw ( _Other );
}

};

  
     
template < class _Ty > class _Ref_count_obj : public _Ref_count_base {
};
 // TEMPLATE FUNCTION make_shared
template<class _Ty _C_CLASS_ARG0> inline
 shared_ptr<_Ty> make_shared(_ARG0_A0_REFREF)
 { // make a shared_ptr
 _Ref_count_obj<_Ty> * _Rx = new _Ref_count_obj<_Ty>(_A0_A1_FWD);
 shared_ptr<_Ty> _Ret;
 _Ret._Resetp0(_Rx->_Getptr(), _Rx);
 return (_Ret);
 }

上面基本上把shared_ptr实现相关的类都列出来了。
从代码中可以看到, shared_ptr和weak_ptr都继承自_Ptr_base, _Ptr_base中有两个指针:
    
_Ty * _Ptr ;
_Ref_count_base * _Rep ;
_Ptr是指向被管理的对象的,_Rep指向控制块。
控制块的基类是_Ref_count_base,这是一个纯虚类。它定义了两个纯虚函数和两个成员变量:
    
virtual void _Destroy () = 0 ;
virtual void _Delete_this () = 0 ;

long _Uses ;
long _Weaks ;
并且定义了以下函数: _Incref/_Incwref/_Decref/_Decwref/_Use_count/_Expired

这些主要负责操作这两个成员变量,当_Used==0时,调用 _Destroy把被管理对象释放掉,当_Weaks==0时,调用_Delete_this把控制块释放掉。

  • shared_ptr<T>(raw_pointer)
int *raw = new int(10); 
shared_ptr<int> pi(raw);
shared_ptr<int> pi(new int(10)); 

这种方式是用户自己先动态分配对象,然后将指针交给shared_ptr管理。
调用的是 _Resetp成员函数,它会分配控制块。
这种方式下的控制块包含: _Uses, _Weaks, 以及一个指向被管理对象的指针。
_Ptr_base里需要有一个指向被管理对象的指针,因为在shared_ptr(和weak_ptr)之间
进行拷贝或赋值的时候需要用到这个指针。
控制块里同样需要一个指向被管理对象的指针,因为当_Uses==0时,我们需要delete被管理的对象。
下面是一张参考图,是从MS的一个开发者的视频教材中截取的。
从图中可以看到,这种使用shared_ptr的方式,存在两次内存分配,一次是用户自己new或类似于shared_ptr<int> pi(new int(10))这种在传递构造函数的参数时new,另一次是在shared_ptr的成员函数_Resetp中。两次内存分配可能会造成内存泄漏,因为如果第二次new失败了,第一次的new是无名参数,我们无法delete。
所以一般不推荐用这种方式,而推荐用make_shared<T>(args)。



下面是一段测试代码,
 shared_ptr<int> pi(new int(100));
 weak_ptr<int> wpi(pi);
 weak_ptr<int> wpi2 = pi, wpi3 = pi;
在VS2010中调试结果:


  • make_shared<T>(args)
make_shared是一个模板函数,在VS2010中它的实现很tricky,通过VS2010可以看到它的真实定义很长很长。上面的 视频中也有提到它的实现,有兴趣可以看看。
从上面的实现代码可以看到, make_shared是先创建了一个 _Ref_count_obj对象,然后用这个对象来填充shared_ptr对象。这个_Ref_count_obj对象既包含了控制块,又包含了被管理的对象,也就是说被管理的对象是由make_shared帮我们创建的。所以它只有一次内存分配,并且是exception-safe的。

(From https://msdn.microsoft.com/en-us/library/hh279669.aspx: Whenever possible, use the make_shared (<memory>) function to create a shared_ptr when the memory resource is created for the first time. make_shared is exception-safe. It uses the same call to allocate the memory for the control block and the resource, and thereby reduces the construction overhead. If you do not use make_shared, then you have to use an explicit new expression to create the object before you pass it to the shared_ptr constructor. )

下面是一段测试代码以及调试结果:
shared_ptr<float> pf = make_shared<float>(3.14);
 weak_ptr<float> wpf = pf;
 weak_ptr<float> wpf2 = pf, wpf3 = pf, wpf4 = pf;



  • 使用注意事项:

考虑下面这个情况:

int *a = new int;

std::shared_ptr<int> p1(a);

std::shared_ptr<int> p2(a);
这种用法是危险的, p1和p2同时管理同一裸指针a,它们有完全独立的两个引用计数器(初始化p2时,用的是裸指针a,于是我们没有任何办法获取p1的引用计数!),于是a会被delete两次,分别由p1和p2的析构导致,这样程序就会crash。

所以千万不要用同一个raw pointer来初始化两个shared_ptr对象。 用make_shared就能避免这种错误用法。

二、weak_ptr

weak_ptr的实现和shared_ptr比较起来相对简单,阅读一下代码就能理解。

这里主要说一下_Weaks的初始值的问题。

从上面的调试结果可以看到,pf显示有1个strong ref,4 weak refs,但它内部的控制块是_Uses = 1, _Weaks = 5。

这是因为_Weaks的初始值是1,而不是0。_Weaks = (weak指针的个数) + (_Uses>0 ? 1 : 0)

根据视频里的讲解,这主要是为了性能优化。

试想如果_Weaks初始化为0,在多线程的情况下,如果_Weaks被更新了,它需要判断_Uses的值来决定是否要delete 控制块,这样每次都要判断,影响效率。

而初始化为1,当_Uses>0时,_Weaks比真正的weak指针的个数多1。当_Uses=0的时候,会将_Weaks减1,这样_Weaks就和weak指针的个数对应了。这样只要_Weaks不为0,就不用delete控制块。

 
  
void _Decref ()
{ // decrement use count
if ( _MT_DECR ( _Mtx , _Uses ) == 0 )
{ // destroy managed resource, decrement weak reference count
_Destroy ();
_Decwref ();
}
}

void _Decwref ()
{ // decrement weak reference count
if ( _MT_DECR ( _Mtx , _Weaks ) == 0 )
_Delete_this ();
}

三、enable_shared_from_this

  • 使用场景
当类A被share_ptr管理,且在类A的成员函数里需要把当前类对象作为参数传给其他函数时,就需要传递一个指向自身的share_ptr。

  • 错误实现
class A: public std::enable_shared_from_this<A>
{
    public:
        A() { cout << "A constructor\n"; }
        ~A() { cout << "A destructor\n"; }
        shared_ptr<A> bad_get_me() {
            return shared_ptr<A>(this);
        }
        shared_ptr<A> good_get_me() {
            return shared_from_this();
        }
};

std::shared_ptr<A> pa = make_shared<A>();
std::shared_ptr<A> pb = pa->bad_get_me(); 

如果调用bad_get_me,则pb和pa是两个不同的智能指针,但他们指向同一个原始指针,这样会造成同一个指针释放两次,从而crash。


  • 正确实现
类A的代码同上。

std::shared_ptr<A> pa = make_shared<A>();
std::shared_ptr<A> pb = pa->good_get_me();

如果调用good_get_me,则pb和pa是共享同一个control block,此时pb相对于是调用拷贝构造函数得到的,这也是shared_ptr的正确用法。

  • 原理

enable_shared_from_this类中包含了一个weak_ptr(当然不能是shared_ptr, 否则即使不用shared_from_this这个功能也会造成引用计数_Uses增加)。
类A继承自这个类,则在A的对象里就自动也包含了一个weak_ptr<A>,它初始时为空。
当我们将A的对象给shared_ptr进行管理的时候,上面的代码中会有_Enable_shared(_Px,_Rx)这个调用,它就会将类A的对象中的weak_ptr<A>指向这个shared_ptr。这样我们就可以通过类A的成员函数shared_from_this来得到shared_ptr<A>了。

下面的代码中有两个_Enable_shared, 一个是函数模板,它使用了enable_shared_from_this类中的类型_EStype来推导, 另一个是普通函数,没有继承enable_shared_from_this类的都是使用这个函数。
  
    
// TEMPLATE CLASS enable_shared_from_this
template < class _Ty > class enable_shared_from_this
{ // provide member functions that create shared_ptr to this
public:
typedef _Ty _EStype ;

shared_ptr < _Ty > shared_from_this ()
{ // return shared_ptr
return ( shared_ptr < _Ty > ( _Wptr ));
}

shared_ptr < const _Ty > shared_from_this () const
{ // return shared_ptr
return ( shared_ptr < const _Ty > ( _Wptr ));
}

protected:
enable_shared_from_this ()
{ // construct (do nothing)
}

enable_shared_from_this ( const enable_shared_from_this & )
{ // construct (do nothing)
}

enable_shared_from_this & operator = ( const enable_shared_from_this & )
{ // assign (do nothing)
return ( * this );
}

~ enable_shared_from_this ()
{ // destroy (do nothing)
}

private:
template < class _Ty1 ,
class _Ty2 >
friend void _Do_enable (
_Ty1 * ,
enable_shared_from_this < _Ty2 >* ,
_Ref_count_base * );

mutable weak_ptr < _Ty > _Wptr ;
};

template < class _Ty1 ,
class _Ty2 >
inline void _Do_enable (
_Ty1 * _Ptr ,
enable_shared_from_this < _Ty2 > * _Es ,
_Ref_count_base * _Refptr )
{ // reset internal weak pointer
_Es -> _Wptr . _Resetw ( _Ptr , _Refptr );
}


template < class _Ty >
inline void _Enable_shared ( _Ty * _Ptr , _Ref_count_base * _Refptr ,
typename _Ty :: _EStype * = 0 )
{ // reset internal weak pointer
if ( _Ptr )
_Do_enable ( _Ptr , ( enable_shared_from_this < typename _Ty :: _EStype >* ) _Ptr , _Refptr );
}

inline void _Enable_shared ( const volatile void * , const volatile void * )
{ // not derived from enable_shared_from_this; do nothing
}



参考链接:

  • http://zh.cppreference.com/w/cpp/memory/shared_ptr
  • http://zh.cppreference.com/w/cpp/memory/enable_shared_from_this
  • http://channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Advanced-STL/C9-Lectures-Stephan-T-Lavavej-Advanced-STL-1-of-n
  • https://msdn.microsoft.com/en-us/library/hh279674.aspx
  • https://msdn.microsoft.com/en-us/library/ee410595.aspx
  • https://msdn.microsoft.com/en-us/library/hh279669.aspx
  • http://www.cnblogs.com/heleifz/p/shared-principle-application.html




                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值