c++ 智能指针- shared_ptr和weak_ptr
2014-10-27 18:10
309 查看
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)
拥有被管理对象的
引用被管理对象的
通过 std::make_shared 和 std::allocate_shared 创建
我们可以通过阅读代码来了解它的具体实现,VS2010的实现是:memory , 这个实现的可读性还是不错的。
(注意:gcc的实现可能和这个不同,即使VS的不同版本之间的实现也可能不同,但原理类似。)
template<class_Ty>class_Ptr_base
{
...
private:
};
template<class_Ty> classshared_ptr:public_Ptr_base<_Ty> {
};
上面基本上把shared_ptr实现相关的类都列出来了。从代码中可以看到,shared_ptr和weak_ptr都继承自_Ptr_base, _Ptr_base中有两个指针:
这些主要负责操作这两个成员变量,当_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)。
下面是一段测试代码,
在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. )
下面是一段测试代码以及调试结果:
使用注意事项:
考虑下面这个情况:
这种用法是危险的,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控制块。
三、enable_shared_from_this
使用场景
当类A被share_ptr管理,且在类A的成员函数里需要把当前类对象作为参数传给其他函数时,就需要传递一个指向自身的share_ptr。
错误实现
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 _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
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 countingprivate: 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> classshared_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_sharedtemplate<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; |
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_thistemplate<class _Ty> class enable_shared_from_this { // provide member functions that create shared_ptr to thispublic: 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
相关文章推荐
- C++智能指针 shared_ptr 与 weak_ptr 原理
- C++智能指针:TR1的 shared_ptr 和 weak_ptr 使用介绍
- C++ 智能指针 shared_ptr unique_ptr weak_ptr
- C++:智能指针-TR1的shared_ptr和weak_ptr使用介绍
- C++:智能指针-TR1的shared_ptr和weak_ptr使用介绍
- [置顶] 从零开始学C++之boost库(一):详解 boost 库智能指针(scoped_ptr<T> 、shared_ptr<T> 、weak_ptr<T> 源码分析)
- [置顶] 从零开始学C++之boost库(一):详解 boost 库智能指针(scoped_ptr<T> 、shared_ptr<T> 、weak_ptr<T> 源码分析)
- C++智能指针(scoped_ptr<T> 、shared_ptr<T> 、weak_ptr<T> )
- C++ 智能指针(shared_ptr/weak_ptr)源码分析
- C++总结8——shared_ptr和weak_ptr智能指针
- 从零开始学C++之boost库(一):详解 boost 库智能指针(scoped_ptr<T> 、shared_ptr<T> 、weak_ptr<T> 源码分析)
- 详解C++各种智能指针: auto_ptr, shared_ptr, weak_ptr, scoped_ptr
- C++学习之智能指针--auto_ptr、scoped_ptr、scoped_array、shared_ptr、shared_array、weak_ptr
- C++:智能指针-TR1的shared_ptr和weak_ptr使用介绍
- C++智能指针(三):weak_ptr--解决shared_ptr循环引用问题
- C++ 智能指针shared-ptr,unique_ptr和weak-ptr
- C++中的智能指针——auto_ptr, unique_ptr, shared_ptr和weak_ptr
- C++智能指针:auto_ptr、shared_ptr、weak_ptr等
- 说说C++智能指针(1): 关于shared_ptr
- Boost智能指针——weak_ptr vs shared_ptr