智能指针原理剖析(一):auto_ptr、unique_ptr
shared_ptr、weak_ptr原理剖析
关于shared_ptr源码剖析的博客有很多,推荐一篇讲解十分详细的博客:从源码理解智能指针(二)—— shared_ptr、weak_ptr。本文在此基础上,对shared_ptr、weak_ptr实现的原理进行总结并画出类关系图,读者可结合两篇博客一起阅读,从而帮助理解。
总体而言,实现shared_ptr、weak_ptr智能指针是通过两个基类_Ref_count_base、_Ptr_base以及它们的派生类来实现的,其结构图如下:
1、抽象类_Ref_count_base
class _Ref_count_base
{ // common code for reference counting
private:
virtual void _Destroy() = 0;
virtual void _Delete_this() = 0;
private:
_Atomic_counter_t _Uses; //强引用计数
_Atomic_counter_t _Weaks; //弱引用计数
protected:
_Ref_count_base()
{ // construct
_Init_atomic_counter(_Uses, 1); //强引用计数初始化为1
_Init_atomic_counter(_Weaks, 1); //弱引用计数初始化为1
}
public:
virtual ~_Ref_count_base() _NOEXCEPT //虚析构函数
{ // ensure that derived classes can be destroyed properly
}
unsigned int _Get_uses() const //获取强引用计数
{ // return use count
return (_Get_atomic_count(_Uses));
}
void _Incref()
{ // increment use count
_MT_INCR(_Mtx, _Uses);// _Uses+1
}
void _Incwref()
{ // increment weak reference count
_MT_INCR(_Mtx, _Weaks);//_Weaks+1
}
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) //判断弱引用计数-1后是否为0
_Delete_this(); //删除计数器自身
}
long _Use_count() const //返回强引用计数
{ // return use count
return (_Get_uses());
}
bool _Expired() const //强引用计数是否为0
{ // return true if _Uses == 0
return (_Get_uses() == 0);
}
virtual void *_Get_deleter(const _XSTD2 type_info&) const
{ // return address of deleter object
return (0);
}
};
_Ref_count_base是派生类_Ref_count、_Ref_count_del、_Ref_count_del_alloc的基类,是计数器的接口。基类中纯虚函数virtual void _Destroy() = 0、virtual void _Delete_this() = 0是实现多态的接口,具体定义在三个子类中实现,分别表示释放资源、删除计数器自身。
_Uses是强引用计数,计算引用资源的shared_ptr个数,_Weaks是弱引用计数,计算引用资源的weak_ptr的个数。 _Uses和_Weaks的增加或减少,在底层中通过一条指令就可以实现,是原子操作。
类中封装了函数_Decref(),表示:若 引用计数 _Uses == 0,就调用_Destroy()来释放资源,并调用_Decwref()递减_Weaks。
类中封装了函数_Decwref(),表示:若弱引用计数_Weaks= =0,就调用_Delete_this()来删除计数器自身。
由_Ref_count_base类的源码可知,释放资源的条件为: _Uses== 0;删除计数器的条件为:_Uses== 0&&_Weaks==0。
1.1、派生类_Ref_count
template<class _Ty>
class _Ref_count
: public _Ref_count_base
{ // handle reference counting for object without deleter
public:
_Ref_count(_Ty *_Px)
: _Ref_count_base(), _Ptr(_Px)
{ // construct
}
private:
virtual void _Destroy()
{ // destroy managed resource
delete _Ptr;
}
virtual void _Delete_this()
{ // destroy self
delete this;
}
_Ty * _Ptr;
};
Ref_count是_Ref_count_base的派生类,定义了资源释放函数_Destroy() 、删除计数器自身函数_Delete_this(),以及一个指向_Ty类型的指针_Ptr,由派生类_Ref_count生成对象时只能通过指针_Ptr来进行构造。
1.2、派生类_Ref_count_del
template<class _Ty,
class _Dx>
class _Ref_count_del
: public _Ref_count_base
{ // handle reference counting for object with deleter
public:
_Ref_count_del(_Ty *_Px, _Dx _Dt)
: _Ref_count_base(), _Ptr(_Px), _Dtor(_Dt)
{ // construct
}
virtual void *_Get_deleter(const _XSTD2 type_info& _Typeid) const
{ // return address of deleter object
return ((void *)(_Typeid == typeid(_Dx) ? &_Dtor : 0));
}
private:
virtual void _Destroy()
{ // destroy managed resource
_Dtor(_Ptr);
}
virtual void _Delete_this()
{ // destroy self
delete this;
}
_Ty * _Ptr;
_Dx _Dtor; // the stored destructor for the controlled object 被控制对象的已析构函数
};
Ref_count_del是_Ref_count_base的派生类,定义了资源释放函数_Destroy() 、删除计数器自身函数_Delete_this(),以及一个指向_Ty类型的指针_Ptr和_Dx类型的删除器_Dtor,由派生类_Ref_count生成对象时只能通过指针_Ptr和删除器_Dtor来进行构造。
1.3、派生类_Ref_count_del_alloc
template<class _Ty,
class _Dx,
class _Alloc>
class _Ref_count_del_alloc
: public _Ref_count_base
{ // handle reference counting for object with deleter and allocator
public:
typedef _Ref_count_del_alloc<_Ty, _Dx, _Alloc> _Myty;
typedef typename _Alloc::template rebind<_Myty>::other _Myalty;
_Ref_count_del_alloc(_Ty *_Px, _Dx _Dt, _Myalty _Al)
: _Ref_count_base(), _Ptr(_Px), _Dtor(_Dt), _Myal(_Al)
{ // construct
}
virtual void *_Get_deleter(const _XSTD2 type_info& _Typeid) const
{ // return address of deleter object
return ((void *)(_Typeid == typeid(_Dx) ? &_Dtor : 0));
}
private:
virtual void _Destroy()
{ // destroy managed resource
_Dtor(_Ptr);
}
virtual void _Delete_this()
{ // destroy self
_Myalty _Al = _Myal;
_Al.destroy(this);
_Al.deallocate(this, 1);
}
_Ty * _Ptr;
_Dx _Dtor; // the stored destructor for the controlled object
_Myalty _Myal; // the stored allocator for this
};
Ref_count_del_alloc是_Ref_count_base的派生类,定义了资源释放函数_Destroy() 、删除计数器自身函数_Delete_this(),以及一个指向_Ty类型的指针_Ptr、_Dx类型的删除器_Dtor、_Myalty类型的分配器_Myal,由派生类_Ref_count生成对象时只能通过指针_Ptr、删除器_Dtor和分配器_Myal来进行构造。
2、基类_Ptr_base
基类_Ptr_base中含有两个成员变量,一个指向_Ty类型的指针_Ptr、一个指向_Ref_count_base类型的计数器指针_Rep。此处的指针_Ptr应与类_Ref_count_base下派生类中的_Ptr相同。
_Ptr_base中的主要函数如下:
_Ptr_base() //无参构造函数
_Ptr_base(_Myt&& _Right) //移动构造函数
template<class _Ty2> _Ptr_base(_Ptr_base<_Ty2>&& _Right) //移动构造函数
_Myt& operator=(_Myt&& _Right) //移动赋值函数
long use_count() const _NOEXCEPT //获取强引用计数
void _Decref() //调用_Rep->_Decref(),减少计数器的强引用计数
void _Decwref() //调用_Rep->_Decwref(),减少计数器的弱引用计数
void _Reset()及其重载版本 //重置当前shared_ptr,减少当前对象持有某资源的强引用计数,增加传入对象持有新资源的强引用计数
void _Resetw()及其重载版本 //重置当前weak_ptr,减少当前对象持有某资源的弱引用计数,增加传入对象持有新资源的弱引用计数
3、shared_ptr
shared_ptr是基类_Ptr_base的派生类,也是用户生成共享智能指针的接口。shared_ptr封装了大量的构造函数、赋值函数,主要供用户构造对象使用:
传参构造函数
shared_ptr() _NOEXCEPT //无参构造函数,调用基类默认构造函数,使_Ptr和_Rep均初始化为0
template<class _Ux> explicit shared_ptr(_Ux *_Px) //用资源指针_Px构造,调用_Ref_count,构造完成后强引用计数为1
template<class _Ux,class _Dx> shared_ptr(_Ux *_Px, _Dx _Dt)//用资源指针和删除器来构造,调用_Ref_count_del,构造完成后强引用计数为1
template<class _Dx> shared_ptr(nullptr_t, _Dx _Dt) //用nullptr和删除器来构造,调用_Ref_count_del,构造完成后强引用计数为1
template<class _Dx,class _Alloc> shared_ptr(nullptr_t, _Dx _Dt, _Alloc _Ax) //用nullptr、删除器、分配器来构造,调用_Ref_count_del_alloc,构造完成后强引用计数为1
template<class _Ux,class _Dx,class _Alloc> shared_ptr(_Ux *_Px, _Dx _Dt, _Alloc _Ax) //用不同类型的资源指针、删除器及分配器来构造,调用_Ref_count_del_alloc,构造完成后强引用计数为1
以上构造函数为传参构造函数:用于从无到有构造一个shared_ptr对象,同时生成一个计数器对象,构造完成后的引用计数和弱引用计数均为1。
拷贝构造函数
template<class _Ty2> shared_ptr(const shared_ptr<_Ty2>& _Right, _Ty *_Px) _NOEXCEPT //传入shared_ptr对象和新的资源指针对构造对象,_Right的引用计数不会改变
shared_ptr(const _Myt& _Other) _NOEXCEPT //拷贝构造,传入shared_ptr对象,当前对象绑定_Other的资源指针和计数器,引用计数+1
template<class _Ty2> explicit shared_ptr(const weak_ptr<_Ty2>& _Other,bool _Throw = true) //用weak_ptr对象来构造shared_ptr,若成功,二者共享资源指针以及计数器,强引用计数+1,否则抛出异常
template<class _Ty2> shared_ptr(const shared_ptr<_Ty2>& _Other, const _Const_tag& _Tag) //传入shared_ptr对象来构造,第二个参数指定使用_Const_tag转换
template<class _Ty2> shared_ptr(const shared_ptr<_Ty2>& _Other, const _Dynamic_tag& _Tag) 传入shared_ptr对象来构造,第二个参数指定使用Dynamic_tag转换
以上构造函数均为拷贝构造函数:均调用_Ptr_base中的_Reset函数,即用现有的对象来构造新的对象,共享资源和计数器,且构造完成后强引用计数+1。
移动构造函数
template<class _Ty2> shared_ptr(auto_ptr<_Ty2>&& _Other) //传入auto_ptr临时对象,构造完成后,临时对象销毁
shared_ptr(_Myt&& _Right) _NOEXCEPT //传入shared_ptr临时对象,构造完成后,临时对象销毁
template<class _Ty2, class = typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,void>::type> shared_ptr(shared_ptr<_Ty2>&& _Right) _NOEXCEPT //传入不同类型的shared_ptr临时对象,构造完成后,临时对象销毁
template<class _Ux,class _Dx> shared_ptr(unique_ptr<_Ux, _Dx>&& _Right) 传入unique_ptr临时对象,获取其资源指针以及删除器,并将_Right的资源指针设置为NULL
以上构造函数均为移动构造函数:用一个临时对象来构造新的对象,继承其资源和计数器,构造完成销毁临时对象,故引用计数不变。
赋值函数
template<class _Ux,class _Dx> _Myt& operator=(unique_ptr<_Ux, _Dx>&& _Right) //传入unique_ptr临时对象,赋值给当前shared_ptr对象,_Right的引用计数不变,自身的引用计数-1
_Myt& operator=(_Myt&& _Right) _NOEXCEPT //传入shared_ptr临时对象,赋值完成后,_Right的引用计数不变,当前对象的引用计数-1
template<class _Ty2> _Myt& operator=(shared_ptr<_Ty2>&& _Right) _NOEXCEPT //传入不同类型的shared_ptr临时对象,赋值完成后,临时对象销毁
_Myt& operator=(const _Myt& _Right) _NOEXCEPT //传入shared_ptr对象,可以是临时对象也可以是非临时对象,_Right的引用计数+1,当前对象的引用计数-1
template<class _Ty2> _Myt& operator=(const shared_ptr<_Ty2>& _Right) _NOEXCEPT //传入不同类型的shared_ptr对象,_Right的引用计数+1,当前对象的引用计数-1
template<class _Ty2> _Myt& operator=(auto_ptr<_Ty2>&& _Right) //传入不同类型的auto_ptr对象,_Right的引用计数不变,当前对象的计数器引用计数-1
由上述赋值函数可知,若为移动赋值函数,传入的对象为临时对象,赋值完成后,临时对象持有资源的引用计数不发生不变化(移动对象std::move),当前对象持有资源的引用计数-1;若为拷贝赋值函数,赋值完成后,传入对象的引用计数+1,当前对象持有资源的引用计数-1。
析构函数
~shared_ptr() _NOEXCEPT
{ // release resource
this->_Decref(); //如果引用计数为0,则在计数器类中进行资源释放
}
某个shared_ptr生命期结束时,会自动调用析构函数,析构函数中会调用计数器的_Decref()函数,资源的强引用计数-1,若强引用计数为0,就会调用_Destroy() 函数释放资源。
综上所述,智能指针shared_ptr的原理为:所有shared_ptr对象中通过同一个计数器来计算资源的引用计数_Uses,可通过拷贝构造函数、移动构造函数、赋值函数来增加新的shared_ptr指向该对象,并自动调用计数器递增引用计数。当某个shared_ptr生命期结束时,会自动调用析构函数,析构函数中会递减它所指向对象的引用计数,若强引用计数为0,析构函数就会调用计数器的_Destroy() 函数释放内存资源。
shared_ptr线程安全
(1)shared_ptr对象的线程安全: 同一个shared_ptr对象被多个线程写不是线程安全的,需要加锁;
(2)计数器的线程安全: 共享引用计数的不同的shared_ptr对象,其引用计数的增加或减少被多个线程写是线程安全的,因为引用计数的增加或减小是原子操作。
shared_ptr应用场景
(1)多个对象共享同一个资源,对象指向资源的创建与销毁是分离的;
(2)容器中存储动态对象。 假设程序中定义一个容器为vector<int*> v;int * p=new int(4);v.push_back( p);,当程序结束对应容器的生命期也结束时,vector容器会发生析构从而释放其中的元素p,但是并不会释放p所指向的内存资源,就会造成内存泄露。当将容器中的指针元素换为shared_ptr时,即vector<shared_ptr< int>>,当vector生命期结束时,析构容器中的元素,而shared_ptr对象可以自动释放其管理的内存资源,就不会发生内存泄露。
(3)管理动态数组。
4、weak_ptr
weak_ptr也是基类_Ptr_base的派生类,是用户生成弱共享智能指针的接口。weak_ptr的定义比shared_ptr简单很多。
weak_ptr的构造函数、赋值函数与shared_ptr类似,但是只能通过其它weak_ptr、shared_ptr来构造,在此不再重复。
weak_ptr中没有重载->操作符,无法获取资源指针,无法访问资源的内存空间,只能判断资源是否有效;
weak_ptr对象与shared_ptr对象共享一个计数器对象,计数器对象在构造第一个shared_ptr对象时生成;
weak_ptr利用expired函数检查资源是否有效,若返回true(即引用计数为0),表示资源已经释放,否则,至少有一个shared_ptr对象持有资源。
weak_ptr不能直接访问资源,而必须调用lock函数检查资源是否被释放,并返回一个shared_ptr对象。若资源已被释放(即expired为true),返回的是一个空shared_ptr对象;否则,返回一个shared_tr,引用计数+1。
同shared_ptr一样,weak_ptr的生命期结束时,会自动调用析构函数,析构函数中会调用计数器的_Decwref()函数,资源的弱引用计数-1,若弱引用计数为0,就会调用_Delete_this() 函数删除计数器对象。
综上所述,智能指针weak_ptr的原理为:weak_ptr对象依赖shared_ptr/weak_ptr对象生成,并自动调用计数器递增弱引用计数。当某个weak_ptr生命期结束时,会自动调用析构函数,析构函数中会通过计数器递减弱引用计数,若弱引用计数为0,析构函数就会调用计数器的_Delete_this()函数删除计数器对象。
不知道大家有没有注意到一个问题,为什么在计数器初始化的时候就要将弱引用计数设置为1呢?
若弱引用计数初始化为0,在强引用计数不为0的情况下,weak_ptr对象生命期都结束时,此时弱引用计数为0,就会删除计数器,但这时share_ptr对象尚在使用、资源也未释放,就会出现内存错误。
5、shared_ptr使用注意事项
在使用shared_ptr指针的过程中,一定要注意避免智能指针的循环引用从而导致的内存泄露,而解决这个问题的方法就是在可能出现循环引用的地方使用weak_ptr来替代shared_ptr。
举例说明:(以下内容来自博客:关于shared_ptr与weak_ptr的使用。)
#include <iostream>
#include <boost/smart_ptr.hpp>
using namespace std;
using namespace boost;
class BB;
class AA
{
public:
AA() { cout << "AA::AA() called" << endl; }
~AA() { cout << "AA::~AA() called" << endl; }
shared_ptr<BB> m_bb_ptr; //!
};
class BB
{
public:
BB() { cout << "BB::BB() called" << endl; }
~BB() { cout << "BB::~BB() called" << endl; }
shared_ptr<AA> m_aa_ptr; //!
};
int main()
{
shared_ptr<AA> ptr_a (new AA);
shared_ptr<BB> ptr_b ( new BB);
cout << "ptr_a use_count: " << ptr_a.use_count() << endl;
cout << "ptr_b use_count: " << ptr_b.use_count() << endl;
//下面两句导致了AA与BB的循环引用,结果就是AA和BB对象都不会析构
ptr_a->m_bb_ptr = ptr_b;
ptr_b->m_aa_ptr = ptr_a;
cout << "ptr_a use_count: " << ptr_a.use_count() << endl;
cout << "ptr_b use_count: " << ptr_b.use_count() << endl;
}
运行结果:
由结果可知:由于类A和B中内部的成员变量shared_ptr各自保存了对方的一次引用,使得shared_ptr对象ptr_a和ptr_b各自的引用计数均为2,程序结束时两者的引用计数均-1但都不为0,因此不会释放各自持有的内存资源,即A和B的析构函数不会被调用,因此就发生了内存泄露。
出现循环引用的解决办法就是将其中一个shared_ptr改为weak_ptr,对应代码如下:
#include <iostream>
#include <boost/smart_ptr.hpp>
using namespace std;
using namespace boost;
class BB;
class AA
{
public:
AA() { cout << "AA::AA() called" << endl; }
~AA() { cout << "AA::~AA() called" << endl; }
weak_ptr<BB> m_bb_ptr; //!
};
class BB
{
public:
BB() { cout << "BB::BB() called" << endl; }
~BB() { cout << "BB::~BB() called" << endl; }
shared_ptr<AA> m_aa_ptr; //!
};
int main()
{
shared_ptr<AA> ptr_a (new AA);
shared_ptr<BB> ptr_b ( new BB);
cout << "ptr_a use_count: " << ptr_a.use_count() << endl;
cout << "ptr_b use_count: " << ptr_b.use_count() << endl;
//下面两句导致了AA与BB的循环引用,结果就是AA和BB对象都不会析构
ptr_a->m_bb_ptr = ptr_b;
ptr_b->m_aa_ptr = ptr_a;
cout << "ptr_a use_count: " << ptr_a.use_count() << endl;
cout << "ptr_b use_count: " << ptr_b.use_count() << endl;
}
运行结果:
从结果可以看到ptr_a的引用计数为2,而ptr_b的引用计数为1,这是因为weak_ptr不会改变强引用计数,因此在程序结束时ptr_a与ptr_b的引用计数均会-1,此时ptr_b的引用计数变为0,就会释放其持有的内存资源即BB发生析构,而BB析构时又会释放其成员变量m_aa_ptr就会使ptr_a的引用计数-1变为0,从而ptr_a就会释放其持有的资源,即AA发生析构。由此可见,此时不会发生内存泄露。
参考博客:share_ptr与weak_ptr的区别与联系。