目录
1.写在前面
3.weak_ptr介绍
5.C++11和Boost的关系
6.内存泄漏
7.如何检测内存泄漏
8.如何避免内存泄漏
上一节介绍智能指针的博客中,我们给出了智能指针实际是把资源委托给对象去管理的一种思想,让对象根据资源的生命周期自动调析构函数来释放资源,然后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根据不同的需求来使用智能指针可以避免人为操控资源的释放导致内存泄漏。
下面这个图可以帮助我们理解什么是循环引用
我们说过,每当有一个对象指向资源时,引用计数++,同理,当我们n1的next指向n2时,n2的引用计数也会++,当我们的n2的prev指向n1时,n1的引用计数也会++。
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;
}
weak_ptr不支持管理资源,所以它当然也不支持访问资源。只支持绑定到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;
}
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;
}
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库的基础上加入一些修改进行。
自始至终,C++11引入智能指针的目的都是预防内存泄漏,我们想,智能指针是把资源交给了对象去管理,当对象的生命周期结束后,让对象自动调析构函数去释放资源,本质就是从资源的手动释放变为了对象管理。
首先介绍一下,
int main()
{
// 申请⼀个1G未释放,这个程序多次运⾏也没啥危害
// 因为程序⻢上就结束,进程结束各种资源也就回收了
char* ptr = new char[1024 * 1024 * 1024];
cout << (void*)ptr << endl;
return 0;
}
避免内存泄漏和我们打疫苗一样,主要以预防为主,其次就是发生内存泄漏后及时解决。