c++11智能指针

目录

1.写在前面

2.shared_ptr的循环引用问题

3.weak_ptr介绍

4.shared_ptr的线程安全问题

5.C++11和Boost的关系

6.内存泄漏

7.如何检测内存泄漏

8.如何避免内存泄漏


1.写在前面

上一节介绍智能指针的博客中,我们给出了智能指针实际是把资源委托给对象去管理的一种思想,让对象根据资源的生命周期自动调析构函数来释放资源,然后c++98设计的智能指针它支持拷贝但是它设计思想非常糟糕,因为它的拷贝是资源管理权的转移,可能造成对象的资源悬空,所以被抛弃掉,C++11又在此基础上给出了更好的智能指针解决方案,我们使用的2个基本就是unique_ptr,就是唯一指针,它只支持一个对象管理一份资源,不支持多个对象去管理一份资源,所以基于这样的原因,它只支持移动,不支持拷贝,设计原理就是把赋值,拷贝构造给禁掉,但是我们还有这样的需求就是让多个对象去管理一份资源,但是这个多个对象管理一份资源可能会导致重复析构,程序崩掉,所以引入引用计数的思想,析构一次引用计数--,拷贝构造一次++,当析构后引用计数归0说明这个时候没有对象管理它的资源了,进行资源的析构,所以这就要求我们当对象管理同一份资源时,要求对象对资源的操作是相互影响的,这就要求我们对资源和引用计数的在堆上管理,不然各个对象都有一份引用计数,彼此互不影响这不符合我们的需求,C++11中为了防止隐式类型转换,还加了explicit把隐式类型转换给禁掉,这样做的原因是当我们用普通指针赋值给智能指针时,防止编译器进行隐式转换把普通指针转换成智能指针。除此之后极端情况下我们的支持拷贝的智能指针可能会出现循环引用,为此我们特地设计出一个weak_ptr来解决这个问题

总结:unique_ptr是独特的指针,它只支持移动不支持拷贝。

shared_ptr是共享指针,它支持移动和拷贝,允许多个对象管理同一份资源。

weak_ptr是为了解决shared_ptr在极端情况下出现循环引用特地设计出来的。为shared_ptr而生。

所以我们实际引用就是unique_ptr和shared_ptr根据不同的需求来使用智能指针可以避免人为操控资源的释放导致内存泄漏。

2.shared_ptr的循环引用问题

下面这个图可以帮助我们理解什么是循环引用

我们说过,每当有一个对象指向资源时,引用计数++,同理,当我们n1的next指向n2时,n2的引用计数也会++,当我们的n2的prev指向n1时,n1的引用计数也会++。

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不参与资源释放管理逻辑,就成功打破了循环引⽤,解决了这⾥的问题

c++11智能指针_第1张图片

struct ListNode
{
int _data;
std::shared_ptr _next;
std::shared_ptr _prev;
// 这⾥改成weak_ptr,当n1->_next = n2;绑定shared_ptr时
// 不增加n2的引⽤计数,不参与资源释放的管理,就不会形成循环引⽤了
/*std::weak_ptr _next;
std::weak_ptr _prev;*/
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
// 循环引⽤ -- 内存泄露
std::shared_ptr n1(new ListNode);
std::shared_ptr n2(new ListNode);
cout << n1.use_count() << endl;
cout << n2.use_count() << endl;
n1->_next = n2;
n2->_prev = n1;
cout << n1.use_count() << endl;

cout << n2.use_count() << endl;
// weak_ptr不⽀持管理资源,不⽀持RAII
// weak_ptr是专⻔绑定shared_ptr,不增加他的引⽤计数,作为⼀些场景的辅助管理
//std::weak_ptr wp(new ListNode);
return 0;
}

3.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 << wp.expired() << endl;
cout << wp.use_count() << 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;
}

4.shared_ptr的线程安全问题

shared_ptr的引⽤计数对象在堆上,如果多个shared_ptr对象在多个线程中,进⾏shared_ptr的拷
⻉析构时会访问修改引⽤计数,就会存在线程安全问题,所以shared_ptr引⽤计数是需要加锁或者
原⼦操作保证线程安全的。
shared_ptr指向的对象也是有线程安全的问题的,但是这个对象的线程安全问题不归shared_ptr
管,它也管不了,应该有外层使⽤shared_ptr的⼈进⾏线程安全的控制。
下⾯的程序会崩溃或者A资源没释放,bit::shared_ptr引⽤计数从int*改成atomic*就可以保证
引⽤计数的线程安全问题,或者使⽤互斥锁加锁也可以。
struct AA
{
int _a1 = 0;
int _a2 = 0;
~AA()
{
cout << "~AA()" << endl;
}
};
int main()
{
bit::shared_ptr p(new AA);
const size_t n = 100000;
mutex mtx;
auto func = [&]()
{
for (size_t i = 0; i < n; ++i)
{
// 这⾥智能指针拷⻉会++计数
bit::shared_ptr copy(p);
{
unique_lock lk(mtx);
copy->_a1++;
copy->_a2++;
}
}
};
thread t1(func);
thread t2(func);
t1.join();
t2.join();
cout << p->_a1 << endl;
cout << p->_a2 << endl;
cout << p.use_count() << endl;
return 0;
}

5.C++11和Boost的关系

boost库相当于一个打游戏的体验服,它里面的东西领先于现有的版本,策划有什么想法先在体验服里试一下看看效果如果效果好的话再搬到正式服,我们的C++98的第一个智能指针就是在boost库中产生的,C++boost库中也给出了unique_ptr和weak_ptr和shared_ptr的想法等,C++TR1引入了shared_ptr,不过注意TR1并不是标准版,unique_ptr对应boost库中的scoped_ptr,这些智能指针的实现原理就是参考C++boost库实现的。在Boost库的基础上加入一些修改进行。

6.内存泄漏

自始至终,C++11引入智能指针的目的都是预防内存泄漏,我们想,智能指针是把资源交给了对象去管理,当对象的生命周期结束后,让对象自动调析构函数去释放资源,本质就是从资源的手动释放变为了对象管理。

首先介绍一下,

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使⽤的内存,⼀般是忘记释 放或者发⽣异常释放程序未能执⾏导致的。内存泄漏并不是指内存在物理上的消失,⽽是应⽤程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因⽽造成了内存的浪费。
内存泄漏的危害:普通程序运⾏⼀会就结束了出现内存泄漏问题也不⼤,进程正常结束,⻚表的射
关系解除,物理内存也可以释放。⻓期运⾏的程序出现内存泄漏,影响很⼤,如操作系统、后台服
务、⻓时间运⾏的客⼾端等等,不断出现内存泄漏会导致可⽤内存不断变少,各种功能响应越来越
慢,最终卡死。
不知道有没有这样的体验我们有的时候使用手机的时候长时间不关机就会一卡一卡的,这个时候就可能是某个软件发生了内存泄漏,如果这个时候我们直接关掉系统,会直接重置,所以手机卡的时候重新关机不卡可能就是某个软件发生了内存泄漏,手机还好说在一些一开启就不停下来的操作系统中,内存泄漏会导致很大的损失,所以我们要避免内存泄漏。
int main()
{
// 申请⼀个1G未释放,这个程序多次运⾏也没啥危害
// 因为程序⻢上就结束,进程结束各种资源也就回收了
char* ptr = new char[1024 * 1024 * 1024];
cout << (void*)ptr << endl;
return 0;
}

7.如何检测内存泄漏

linux下内存泄漏检测: linux下⼏款内存泄漏检测⼯具
windows下使⽤第三⽅⼯具: windows下的内存泄露检测⼯具VLD使⽤_windows内存泄漏检测⼯
具-CSDN博客

8.如何避免内存泄漏

避免内存泄漏和我们打疫苗一样,主要以预防为主,其次就是发生内存泄漏后及时解决。

⼯程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理
想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下⼀条智能指针来管理
才有保证。
尽量使⽤智能指针来管理资源,如果⾃⼰场景⽐较特殊,采⽤RAII思想⾃⼰造个轮⼦管理。
定期使⽤内存泄漏⼯具检测,尤其是每次项⽬快上线前,不过有些⼯具不够靠谱,或者是收费。
总结⼀下:内存泄漏⾮常常⻅,解决⽅案分为两种:1、事前预防型。如智能指针等。2、事后查错
型。如泄漏检测⼯具。
最后的最后,如果你觉得这篇博客对你有帮助,请帮助我点赞收藏+转发,这对我帮助很大,谢谢!这就是C++学习的全部了,如果以后有新的知识补充,还是会继续更新的,拜拜!!!

你可能感兴趣的:(C++学习,c++,开发语言)