起初在C++标准库里面是没有智能指针的,直到C++11才加入了shared_ptr和unique_ptr以及weak_ptr。
最早的智能指针在Boost库里面,Boost库是为C++标准库提供扩展的一些C++程序的总称,由Boost社区组织开发维护。
“智能”指针看上去是指针,实际上是附加了语义的对象。shared_ptr与scoped_ptr一样包装了new操作符在堆上分配的动态对象,但它实现的是引用计数型的智能指针 ,可以被自由地拷贝和赋值,在任意的地方共享它,当没有代码使用(引用计数为0)它时才删除被包装的动态分配的对象。shared_ptr也可以安全地放到标准容器中,并弥补了auto_ptr因为转移语义而不能把指针作为STL容器元素的缺陷。任何情况下都不要使用auto_ptr。
一、模拟实现shared_ptr的基本功能:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
template<class T>
class SharpedPtr
{
public:
SharpedPtr(T* ptr = 0)//构造函数
:_ptr(ptr)
, _pCount(NULL)
{
if (ptr)
{
_pCount = new int(1);
}
}
SharpedPtr(const SharpedPtr<T>& ap)//拷贝构造
:_ptr(ap._ptr)
, _pCount(ap._pCount)
{
if (_ptr)
{
++UseCount();
}
}
//ap1 = ap2;赋值运算符重载
//①ap1内部封装的指针为空
//②ap1独占一块空间
//③ap1与其他对象共享一块空间
SharpedPtr& operator=(const SharpedPtr<int>& ap)
{
if (this != &ap)
{
if (_ptr)
{
Release();
}
//ap1与其他对象共享一块空间
_ptr = ap._ptr;
_pCount = ap._pCount;
++UseCount();
}
return *this;
}
//析构函数
~SharpedPtr()
{
Release();
}
//检查引用计数并释放空间
void Release()
{
if (0 == --UseCount())
{
delete _pCount;
_pCount = NULL;
delete _ptr;
_ptr = NULL;
}
}
//获取引用计数
int& UseCount()
{
return *_pCount;
}
//为了使其更像一个指针,所完成的基本操作
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
private:
T* _ptr;
int* _pCount;
};
void TestSharpedPtr()
{
SharpedPtr<int> ap1(new int(10));
SharpedPtr<int> ap2(new int(20));
SharpedPtr<int> ap3(ap1);
SharpedPtr<int> ap4;
ap4 = ap1;
*ap1 = 1;
*ap2 = 2;
*ap3 = 3;
*ap4 = 4;
}
int main()
{
TestSharpedPtr();
return 0;
}
虽然我们上面的程序看起来还可以,但实际上还存在以下问题:
但是还存在以下问题:
- 线程安全问题:
shared_ptr 本身不是 100% 线程安全的。它的引用计数本身是安全且无锁的,但对象的读写则不是,因为 shared_ptr 有两个数据成员,读写操作不能原子化。shared_ptr 的线程安全级别和内建类型、标准库容器、string 一样,即:
- 一个 shared_ptr 实体可被多个线程同时读取;
- 两个的 shared_ptr 实体可以被两个线程同时写入,“析构”算写操作;
- 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁。
2.对于管理的特定类型的指针的释放不合理造成的程序崩溃:
①
我们都会有一种普遍的认知,shared_ptr智能指针只能用来管理动态开辟的空间,实际不然,shared_ptr是用来管理资源的,这里的资源并不仅仅是动态开辟的空间,还有例如:打开文件,其维护一个文件指针,那么在程序快要结束时,需要关闭文件;
还有就是使用malloc来动态开辟的空间,需要使用free来释放,否则,不管管理什么资源,都使用delete来释放空间,有时就会使得程序崩溃。
所以呢,我们就需要为其定制删除器
。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
//为解决文件指针
struct Fclose
{
void operator()(FILE*& fp)
{
cout<< "Fclose()" << endl;
fclose(fp);
fp = NULL;
}
};
//为解决malloc开辟的空间
template<class T>
struct Free
{
void operator()(T*& p)
{
free(p);
p = NULL;
}
};
//一般情况下(使用new动态开辟的空间)
template<class T>
class Delete
{
public :
void operator()(T*& p)
{
delete p;
p = NULL;
}
};
template<class T, class Destory = Delete<T> >
class SharedPtr
{
public :
SharedPtr(T* ptr = 0)//构造函数
:_ptr(ptr)
,_pCount(NULL)
{
if (_ptr)
{
_pCount = new int(1);
}
}
SharedPtr(const SharedPtr<T>& sp)//拷贝构造函数
:_ptr(sp._ptr)
, _pCount(sp._pCount)
{
if (_ptr)
{
++GetRef();
}
}
//sp1 = sp2
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
{
//可有三种情况:①sp1._ptr = NULL ②sp1的引用计数为1 ③sp1的引用计数大于1
if (this != &sp)
{
Release();
_ptr = sp._ptr;
_pCount = sp._pCount;
++GetRef();
}
return *this;
}
//辅助函数
void Release()
{
if (_ptr && --GetRef() == 0)
{
Destory()(_ptr);
delete _pCount;
_pCount = NULL;
}
}
//析构函数
~SharedPtr()
{
Release();
}
int& GetRef()
{
return *_pCount;
}
private:
T* _ptr;
int* _pCount;
};
void Test2()
{
FILE* sp1 = fopen("test.txt", "rb");
SharedPtr<FILE, Fclose> sp2(sp1);
int* sp3 = (int*)malloc(sizeof(int));
SharedPtr<int, Free<int> >sp4(sp3);
int* sp5 = new int(1);
SharedPtr<int> sp6(sp5);
}
int main()
{
Test2();
return 0;
}
3.循环引用问题;
先来看下面一个示例:
下面的代码使用的都是c++标准库里的智能指针,源码也是。boost库的智能指针和c++标准库中的智能指针在底层实现机制是一样的。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include"boost/shared_ptr.hpp"
struct Node
{
Node(int value)
:_value(value)
{
cout << "Node()" << endl;
}
~Node()
{
cout << "~Node()" << endl;
}
shared_ptr<Node> _prev;
shared_ptr<Node> _next;
int _value;
};
void Test2()
{
shared_ptr<Node> sp1(new Node(1));
shared_ptr<Node> sp2(new Node(2));
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
sp1->_next = sp2;
sp2->_prev = sp1;
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
}
int main()
{
Test2();
return 0;
}
可以看出,在这里我们使用了双向链表来说明这个问题,并且双向链表中的指针都是使用shared_ptr来维护的,在这里我使用的是标准库里的
shared_ptr智能指针。
从下图可以看出,构造的sp1和sp2在出它们的作用域时(即Test2())都没有被析构,从而造成了内存泄漏。
由于先构造的后释放,后构造的先释放可知,先释放的是sp2,那么因为它的引用计数为2,减去1之后就成为了1,不能释放空间,因为还有其他的对象在管理这块空间。但是sp2这个变量已经被销毁,因为它是栈上的变量,但是sp2管理的堆上的空间并没有释放。
接下来释放sp1,同样,先检查引用计数,由于sp1的引用计数也是2,所以减1后成为1,也不会释放sp1管理的动态空间。
通俗点讲:就是sp2要释放,那么必须等p1释放了,而sp1要释放,必须等sp2释放,所以,最终,它们两个都没有释放空间。
所以,造成了内存泄漏。
下面让我们一起来剖析源码是怎样实现这种情况的?
上图只给出了简单的过程,下面贴出调用过程完整的源码:
shared_ptr() _NOEXCEPT
{ // construct empty shared_ptr
}
_Ptr_base()
: _Ptr(0), _Rep(0)
{ // construct
}
上面的构造函数连续调用了两次,构造节点内的_prev和_next。
template<class _Ux>
explicit shared_ptr(_Ux *_Px)
{ // construct shared_ptr object that owns _Px
_Resetp(_Px);
}
_Ptr_base()
: _Ptr(0), _Rep(0)
{ // construct
}
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
}
_Ref_count(_Ty *_Px)
: _Ref_count_base(), _Ptr(_Px)
{ // construct
}
_Ref_count_base()
{ // construct
_Init_atomic_counter(_Uses, 1);
_Init_atomic_counter(_Weaks, 1);
}
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;
}
inline void _Enable_shared(const volatile void *, const volatile void *)
{ // not derived from enable_shared_from_this; do nothing
}
所以,构造智能指针sp1主要完成两件事情:
1、给引用计数开辟空间,并初始化uses和weaks均为1
2、将节点的空间交给_Rep来管理,将引用计数的空间交给_Ptr来管理。
sp2和sp1的构造过程相同。
同样:下面贴出上述过程的源码:
_Ty *operator->() const _NOEXCEPT
{ // return pointer to resource
return (this->_Get());
}
_Myt& operator=(const _Myt& _Right) _NOEXCEPT
{ // assign shared ownership of resource owned by _Right
shared_ptr(_Right).swap(*this);
return (*this);
}
shared_ptr(const _Myt& _Other) _NOEXCEPT
{ // construct shared_ptr object that owns same resource as _Other
this->_Reset(_Other);
}
_Ptr_base()
: _Ptr(0), _Rep(0)
{ // construct
}
template<class _Ty2>
void _Reset(const _Ptr_base<_Ty2>& _Other)
{ // release resource and take ownership of _Other._Ptr
_Reset(_Other._Ptr, _Other._Rep);
}
void _Reset(_Ty *_Other_ptr, _Ref_count_base *_Other_rep)
{ // release resource and take _Other_ptr through _Other_rep
if (_Other_rep)
_Other_rep->_Incref();
_Reset0(_Other_ptr, _Other_rep);
}
void _Incref()
{ // increment use count
_MT_INCR(_Mtx, _Uses);
}
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;
}
上述调用过程,主要完成:
1.将sp2管理的引用计数中的uses加至2
2.sp1中的_next节点中的_Rep管理sp2的引用计数,_next节点中的_Ptr管理sp2节点
sp2->_prev = sp1;
其实调用过程同sp1->_next = sp2调用过程,主要完成:
1.将sp1管理的引用计数中的uses加至2
2.sp2中的_prev节点中的_Rep管理sp1的引用计数,_prev节点中的_Ptr管理sp1节点.
总体结构图如下:
图1
下面我们一起来看看析构的过程:
先析构sp2,下面是析构sp2的过程,可以看出没有释放空间(节点空间和引用计数空间),sp2析构完成之后,再去析构sp1,sp1的析构过程和sp2的析构过程相同,所以由此可知,一共有四块动态开辟的空间,但是都没有释放。
这里只贴出sp2的析构过程。
~shared_ptr() _NOEXCEPT
{ // release resource
this->_Decref();
}
void _Decref()
{ // decrement reference count
if (_Rep != 0)
_Rep->_Decref();
}
void _Decref()
{ // decrement use count
if (_MT_DECR(_Mtx, _Uses) == 0)//uses为2,经过这条语句之后减为1
{ // destroy managed resource, decrement weak reference count
_Destroy();//不执行
_Decwref();//不执行
}
}
由此引入weak_ptr,可以这么说,weak_ptr是为了shared_ptr而生,weak_ptr就是为解决shared_ptr的循环引用问题而产生的。
它必须依赖于shared_ptr,不能直接管理一块动态开辟的空间。
-------------------------------------------------------------------------
下面让我们一起来看weak_ptr是如何来解决循环引用的。
因为下面的过程和上面的过程大多数相同,所以在这里就不详细讲解了。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include"boost/shared_ptr.hpp"
struct Node
{
Node(int value)
:_value(value)
{
cout << "Node()" << endl;
}
~Node()
{
cout << "~Node()" << endl;
}
weak_ptr<Node> _prev;
weak_ptr<Node> _next;
int _value;
};
void Test2()
{
shared_ptr<Node> sp1(new Node(1));
shared_ptr<Node> sp2(new Node(2));
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
sp1->_next = sp2;
sp2->_prev = sp1;
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
}
int main()
{
Test2();
return 0;
}
使用源码分析上述过程:
先来看构造sp1和sp2的过程:
shared_ptr<Node> sp1(new Node(1));
shared_ptr<Node> sp2(new Node(2));
Node(int value)
:_value(value)
{
cout << "Node()" << endl;
}
//与上面的区别在这里(Node里的_prev和_next分别为weak_ptr智能指针)
weak_ptr() _NOEXCEPT
{ // construct empty weak_ptr object
}
_Ptr_base()
: _Ptr(0), _Rep(0)
{ // construct
}
上面过程同样连续执行两次(构造_prev和_next)
下面过程也是执行两次(构造sp1和sp2),和上面的过程相同
template<class _Ux>
explicit shared_ptr(_Ux *_Px)
{ // construct shared_ptr object that owns _Px
_Resetp(_Px);
}
_Ptr_base()
: _Ptr(0), _Rep(0)
{ // construct
}
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
}
_Ref_count(_Ty *_Px)
: _Ref_count_base(), _Ptr(_Px)
{ // construct
}
_Ref_count_base()
{ // construct
_Init_atomic_counter(_Uses, 1);
_Init_atomic_counter(_Weaks, 1);
}
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;
}
inline void _Enable_shared(const volatile void *, const volatile void *)
{ // not derived from enable_shared_from_this; do nothing
}
sp1->_next = sp2;
sp2->_prev = sp1;
源码分析上面两个语句的执行过程:
sp1->_next = sp2;
_Ty *operator->() const _NOEXCEPT
{ // return pointer to resource
return (this->_Get());
}
template<class _Ty2>
weak_ptr& operator=(const shared_ptr<_Ty2>& _Right) _NOEXCEPT//_Right就是sp2
{ // assign from _Right
this->_Resetw(_Right);
return (*this);
}
template<class _Ty2>
void _Resetw(const _Ptr_base<_Ty2>& _Other)//_Other为sp2
{ // release weak reference to resource and take _Other._Ptr
_Resetw(_Other._Ptr, _Other._Rep);
}
template<class _Ty2>
void _Resetw(_Ty2 *_Other_ptr, _Ref_count_base *_Other_rep)
{ // point to _Other_ptr through _Other_rep
if (_Other_rep)//增加sp2的弱引用计数,sp2.weaks加1 = 2
_Other_rep->_Incwref();
if (_Rep != 0)//sp1->next._Rep为0
_Rep->_Decwref();//不执行
_Rep = _Other_rep;//sp1->_next._Rep = sp2._Rep
_Ptr = _Other_ptr;//sp1->_next._Ptr = sp2._Ptr
}
void _Incwref()
{ // increment weak reference count
_MT_INCR(_Mtx, _Weaks);//将weaks从1修改为2
}
sp2->_prev = sp1;
_Ty *operator->() const _NOEXCEPT
{ // return pointer to resource
return (this->_Get());
}
template<class _Ty2>//_Right就是sp1
weak_ptr& operator=(const shared_ptr<_Ty2>& _Right) _NOEXCEPT
{ // assign from _Right
this->_Resetw(_Right);
return (*this);
}
template<class _Ty2>//_Other为sp1
void _Resetw(const _Ptr_base<_Ty2>& _Other)
{ // release weak reference to resource and take _Other._Ptr
_Resetw(_Other._Ptr, _Other._Rep);
}
template<class _Ty2>
void _Resetw(_Ty2 *_Other_ptr, _Ref_count_base *_Other_rep)
{ // point to _Other_ptr through _Other_rep
if (_Other_rep)//增加sp1的弱引用计数,sp1.weaks加1 = 2
_Other_rep->_Incwref();
if (_Rep != 0)//sp2->prev._Rep为0
_Rep->_Decwref();//不执行
_Rep = _Other_rep;//sp2->_prev._Rep = sp1._Rep
_Ptr = _Other_ptr;//sp2->_prev._Ptr = sp1._Ptr
}
void _Incwref()
{ // increment weak reference count
_MT_INCR(_Mtx, _Weaks);
}
图2
分析图2与图1的区别:
1)sp1和sp2节点内的_prev和_next均为弱引用计数(_weak_ptr)
2)sp1和sp2的引用计数中的_weaks为2,_uses为1,和上面相反。
源码分析析构过程:
//析构sp2
~shared_ptr()
{ // release resource
this->_Decref();
}
void _Decref()
{ // decrement reference count
if (_Rep != 0)//sp2._Rep不为0
_Rep->_Decref();
}
void _Decref()
{ // decrement use count
if (_MT_DECR(_Mtx, _Uses) == 0)//sp2.uses从1减为0
{ // destroy managed resource, decrement weak reference count
_Destroy();
_Decwref();
}
}
//释放节点空间(清理节点内资源(包括sp2->next和sp2->prev))
virtual void _Destroy()
{ // destroy managed resource
delete _Ptr;
}
-------------------------------------------------------------------------------------
下面都是清理sp2节点内的资源
~Node()
{
cout << "~Node()" << endl;
}
~weak_ptr()//清理sp2._next(弱智能指针)
{ // release resource
this->_Decwref();
}
void _Decwref()
{ // decrement weak reference count
if (_Rep != 0)//sp2->next._Rep为0
_Rep->_Decwref();//不执行
}
~weak_ptr()//清理sp2._prev
{ // release resource
this->_Decwref();
}
void _Decwref()/sp2->prev._Rep不为0(即就是sp1的引用计数空间存在)
{ // decrement weak reference count
if (_Rep != 0)/
_Rep->_Decwref();
}
void _Decwref()//sp2->prev._Weaks从2减为1(即就是将sp1._Weaks减为1)
{ // decrement weak reference count
if (_MT_DECR(_Mtx, _Weaks) == 0)
_Delete_this();//不执行
}
调用operator delete释放节点sp2的空间
void _Decwref()//释放sp2引用计数空间
{ // decrement weak reference count
if (_MT_DECR(_Mtx, _Weaks) == 0)//sp2._weaks从2减为1
_Delete_this();//不执行(也就是没有释放sp2的引用计数)
}
----------------------------------------------------------------------------
析构sp1
~shared_ptr()
{ // release resource
this->_Decref();
}
void _Decref()//减sp1的引用计数
{ // decrement reference count
if (_Rep != 0)//sp1._Rep不为0(sp1的引用计数不为空)
_Rep->_Decref();m
}
void _Decref()
{ // decrement use count
if (_MT_DECR(_Mtx, _Uses) == 0)//sp1._uses从1减为0
{ // destroy managed resource, decrement weak reference count
_Destroy();//释放节点空间
_Decwref();//释放sp1的引用计数
}
}
-------------------------------------------------------------------------------
清理sp1节点内资源
virtual void _Destroy()
{ // destroy managed resource
delete _Ptr;//释放sp1节点的空间(需要去清理节点内的资源(包括sp1->_next和sp1->_prev))
}
~weak_ptr()//清理sp1->next
{ // release resource
this->_Decwref();
}
void _Decwref()
{ // decrement weak reference count
if (_MT_DECR(_Mtx, _Weaks) == 0)//sp1->next._weaks从1减为0(即就是sp2._weaks减为0)
_Delete_this();//执行
}
virtual void _Delete_this()//释放了sp1->_next._Rep空间(即就是sp2的引用计数空间)
{ // destroy self
delete this;
}
~weak_ptr()//清理sp1._prev
{ // release resource
this->_Decwref();
}
void _Decwref()
{ // decrement weak reference count
if (_Rep != 0)//sp1->_prev._Rep为空
_Rep->_Decwref();//不执行
}
调用operator delete释放节点sp1空间
-----------------------------------------------------------------------------------------------------------
释放sp1引用计数空间
void _Decwref()
{ // decrement weak reference count
if (_MT_DECR(_Mtx, _Weaks) == 0)//sp1._weaks从1减为0
_Delete_this();
}
virtual void _Delete_this()//释放sp1引用计数的空间
{ // destroy self
delete this;
}
分析不同之处:
1)执行下面两条语句
:
sp1->_next = sp2;
sp2->_prev = sp1;
都是将对方的_weaks加至2
。
2)释放节点空间(关注uses的大小,若可以减为0,则释放节点空间)
释放引用计数空间(关注_weaks的大小,若可以减为0,则释放引用计数的空间)
从而解决了循环引用的问题。
下面用图简单说明下析构过程:
sp2的析构过程:

sp1的析构过程:
对于c++标准库里shared_ptr和weak_ptr的继承关系简单图示:
总结:
在以下情况时使用 shared_ptr :
-
当有多个使用者使用同一个对象,而没有一个明显的拥有者时
-
当要把指针存入标准库容器时
-
当要传送对象到库或从库获取对象,而没有明确的所有权时
-
当管理一些需要特殊清除方式的资源时
通过定制删除器的帮助。
由于看的源码太少,所以文中对于源码的理解不到位的地方还请各位大神指正!