C++智能指针之shared_Ptr的原理以及简单实现
文章目录
1.引言
关于智能指针,它是一种C++对象,它的行为类似于指针,但是它具有自动管理内存的能力。智能指针常见的思想是RAII,即将资源的创建与释放与对象的生命周期所绑定的一种解决可能存在的资源泄露等问题的策略。能够自动管理内存的大致原理是:对象调用构造,资源就保存,对象不使用之后调用析构,则资源顺带释放。
shared_Ptr又是其中一种支持拷贝的智能指针。它允许多个指针同时共享同一个对象,同时确保当所有指向该对象的指针都被销毁时,该对象也会被正确地释放。实现它的原理是采用了引用计数的办法。
引用计数:引用计数是一种内存管理办法和思想。记录有多少个指针指向它,存在即+1,不指向则-1,归0后则释放资源。
但是缺点是存在循环引用的问题,所以shared_Ptr一般与weak_Ptr配合协助使用。
什么是循环引用? 循环引用模拟场景:假设有两双向链表个节点:
std::shared_Ptr<Node> n1(new Node);
std::shared_Ptr<Node> n2(new Node);
右边节点n2什么时候析构? 右边节点n2等待prev所指向n1节点析构
左边节点n2什么时候析构? 左边节点n2等待next所指向n2节点析构
这样互相依靠而引用计数又不减少的情况就是循环引用。
2.实现思路
首先shared_Ptr作为一个可以拷贝的支持引用计数的智能指针,需要满足四大特性:
1.类似RAII的思路
2.像指针一样使用
3.能够拷贝(且必须是浅拷贝)
4.引用计数
第一点代表了我们要实现:普通构造创建资源、析构释放资源。
第二点代表了我们要实现:operator 、 operator->等运算符的重载。*
第三点代表了我们要实现:拷贝构造、赋值重载。
第四点代表了我们要实现:计数器Count。
值得关注的是第四点:Count不能是普通int型变量,不然每个对象都会有不同的一个count。也不能是Static型变量,否则所有资源都是共享的同一份计数器。我们想要的计数器必须是同一份资源的计数器,而且是多个对象所共享的,所以只能只用int* 类型的计数器。
还由于shared_Ptr本身是线程安全的,所以我们在实现的过程中也必须上锁。
3.具体实现
3.1类结构
template<class T>
class shared_ptr //shared_ptr 本身是线程安全的
{
public:
shared_ptr(T* ptr) //普通构造
:_ptr(ptr)
,_pcount(new int(1))
,_pmtx(new mutex)
{}
shared_ptr(const shared_ptr<T>& sp) //拷贝构造
:_ptr(sp._ptr)
,_pcount(sp._pcount)
,_pmtx(sp._pmtx)
{
}
shared_ptr& operator=(const shared_ptr<T>& sp) //赋值重载
{}
void Release() /放资源单独写出来,后面放进析构好方便加锁,因为本身是线程安全的
{}
void AddCount() //计数器++
{}
~shared_ptr() //析构
{}
T& operator*()//*重载
{}
T* operator->()//->重载
{}
private:
T* _ptr; //指针本身
int* _pcount; //计数器
mutex* _pmtx; //锁
};
3.2计数器++函数AddCount()函数
整个++过程中需要上锁。
void AddCount()
{
_pmtx->lock();
++(*_pcount);
_pmtx->unlock();
}
3.3释放资源函数Release()函数
释放资源的过程中也需要上锁
值得注意的是在–的过程中技术器等于零之后才开始删除指针与计数器
而且由于上了锁,所以需要设置一个flag标志位,因为不能在unclock解锁之前就delete掉锁,否则资源泄露。
具体请看代码:
void Release()
{
_pmtx->lock();
bool DeleteFlag = false; //因为释放里面已经上锁了,不能在unclock之前就delete掉锁,否则资源泄露,所以立个标志位
if (--(*_pcount) == 0)
{
cout << "delete" << ":" << _ptr << endl;
delete _ptr; //删掉指针与计数器
delete _pcount;
DeleteFlag = true;//因为释放里面已经上锁了,不能在unclock之前就delete掉锁,否则资源泄露,所以立个标志位
}
_pmtx->unlock();
if (DeleteFlag = true)
delete _pmtx;//因为释放里面已经上锁了,不能在unclock之前就delete掉锁,否则资源泄露,所以立个标志位
}
3.4普通构造&&拷贝构造&&赋值重载
最开始的时候计数器设置为1,然后拷贝构造的时候就需要++计数器了
使用赋值=的时候也一样,因为这是引用计数特色玩法
shared_ptr(T* ptr) //普通构造
:_ptr(ptr)
,_pcount(new int(1))
,_pmtx(new mutex)
{}
shared_ptr(const shared_ptr<T>& sp) //拷贝构造
:_ptr(sp._ptr)
,_pcount(sp._pcount)
,_pmtx(sp._pmtx)
{
AddCount();
}
shared_ptr& operator=(const shared_ptr<T>& sp) //赋值重载
{
if (_ptr != sp._ptr) //防止sp1 = sp1 赋值自己
{
Release();
_ptr = sp._ptr;
_pcount = sp._pcount;
_pmtx = sp._pmtx;
AddCount();
}
return *this;
}
~shared_ptr()
{
Release();
}
3.5*运算符和->运算符
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
4.测试
我们采用一下代码,看看是否是同一份资源是同一个计数器,不同资源是不同计数器:
void test_shared()
{
shared_ptr<int> sp1(new int(1));
shared_ptr<int> sp2(sp1);
shared_ptr<int> sp3(sp2);
shared_ptr<int> sp4(new int(10));
sp4 = sp1;
sp1 = sp2;
}
按理说:sp1,sp2,sp3应该资源一样,sp4应该资源不一致
而且计数器也应该不一致
自此为止,说明测试成功!