以下测试代码gitee仓库地址:https://gitee.com/tgwTTT/c-lreant/tree/master/Smart%20pointer
在C++使用内存的时候很容易出现野指针、空指针、内存泄露。所以C++11引入了智能指针来管理内存。在c++11中一共引入了3种指针,即unique_ptr 、shared_ptr、weak_ptr.
一.unique_ptr:他的特点的不⽀持拷⻉,只⽀持移动。如果不需要拷⻉的场景就⾮常建议使⽤他。
struct Date
{
int _year;
int _month;
int _day;
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
~Date()
{
cout << "~Date()" << endl;
}
};
unique_ptr up1(new Date);
// 不⽀持拷⻉
//unique_ptr up2(up1);
// ⽀持移动,但是移动后up1也悬空,所以使⽤移动要谨慎
unique_ptr up3(move(up1));
shared_unique:他的特点是⽀持拷⻉,也⽀持移动。如果需要拷⻉的场景就需要使⽤他了。底层是⽤引⽤计数的⽅式实现的。
weak_ptr:不能⽤它直接管理资源,weak_ptr的产⽣本质是要解决shared_ptr的⼀个循环引⽤导致内存泄漏的问题。具体细节下⾯我们再细讲
下面我们来了解一下shared_unique的底层实现:
namespace bit {
template
class shared_ptr
{
public :
explicit shared_ptr(T* ptr)
: _ptr(ptr)
, _pcount(new int(1))
{}
shared_ptr(const shared_ptr& sp)//拷贝构造计数++
:_ptr(sp._ptr)
, _pcount(sp._pcount)
{
++(*_pcount);
}
~shared_ptr()
{
cout << "计数--" << endl;
if (--(*_pcount) == 0) {
delete _ptr;
delete _pcount;
}
}
T* get() const
{
return _ptr;
}
int use_count() const
{
return *_pcount;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
shared_ptr&operator=(const shared_ptr&sp)
{
if (sp._ptr != _ptr) {
if (--(*_pcount) == 0) {
cout << "计数--" << endl;
delete _ptr;
delete _pcount;
}
_pcount = sp._pcount;
_ptr = sp._ptr;
++(*_pcount);
}
return *this;
}
private:
T* _ptr;
int* _pcount;
};
}
shared_ptr:的底层原理是利用一个pcount计数指针,每当函数进行拷贝构造或赋值时就++,当pcount==0,即所有指向地址的指针被析构时,pcount--.直到零时delete pcount 和ptr指针。
但是此时我们会出现一个新的问题,我们无法delete 那些特殊类如:[],file等,此时我们就必须再增加一个函数去保证他能析构成功,此时我们就必须引入如下代码:
在类里面添加:
template
shared_ptr(T* ptr, D del)
: _ptr(ptr)
, _pcount(new int(1))
,_del(del)
{}
再利用functional类里面的封装一个对象void(T*):
function _del = [](T* ptr) {
delete[] ptr;
};
此时代码就趋近于完善。
有了代码后我们回到上面的问题循环引用:当我们有两个有智能指针管理的类时:


1. 右边的节点什么时候释放呢,左边节点中的_next管着呢,_next析构后,右边的节点就释放了。
2. _next什么时候析构呢,_next是左边节点的的成员,左边节点释放,_next就析构了。
3. 左边节点什么时候释放呢,左边节点由右边节点中的_prev管着呢,_prev析构后,左边的节点就释
放了。
4. _prev什么时候析构呢,_prev是右边节点的成员,右边节点释放,_prev就析构了。
⾄此逻辑上成功形成回旋镖似的循环引⽤,谁都不会释放就形成了循环引⽤,导致内存泄漏
把ListNode结构体中的_next和_prev改成weak_ptr,weak_ptr绑定到shared_ptr时不会增加它的
引⽤计数,_next和_prev不参与资源释放管理逻辑,就成功打破了循环引⽤,解决了这⾥的问题
weak_ptr:weak_ptr构造时不⽀持绑定到资源,只⽀持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引⽤计数,那么就可以解决上述的循环引⽤问题。weak_ptr也没有重载operator*和operator->等,因为他不参与资源管理,那么如果他绑定的shared_ptr已经释放了资源,那么他去访问资源就是很危险的。weak_ptr⽀持expired检查指向的资源是否过期,use_count也可获取shared_ptr的引⽤计数,weak_ptr想访问资源时,可以调⽤lock返回⼀个管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是⼀个空对象,如果资源没有释放,则通过返回的shared_ptr访问资源是安全的,下面是测试代码:
int main() {
std::shared_ptr sp1(new string("111111"));
std::shared_ptr sp2(sp1);
std::weak_ptr wp = sp1;
cout << wp.expired() << endl;
cout << wp.use_count() << endl;
// sp1和sp2都指向了其他资源,则weak_ptr就过期了
sp1 = make_shared("222222");
cout << "2222222" << endl;
cout << wp.expired() << endl;
cout << wp.use_count() << endl;
cout << "3333333" << endl;
sp2 = make_shared("333333");
cout << wp.expired() << endl;
cout << wp.use_count() << endl;
wp = sp1;
//std::shared_ptr sp3 = wp.lock();
auto sp3 = wp.lock();
cout << wp.expired() << endl;
cout << wp.use_count() << endl;
*sp3 += "###";
cout << *sp1 << endl;
return 0;
}

今天的更新就到这里了,如有错误,欢迎指出!!!
评论
还没有任何评论,你来说两句吧!