首先我们要知道,循环引用问题是shared_ptr智能指针引起的。shared_ptr的一个最大的陷阱是循环引用,循环引用会导致堆内存无法正确释放,导致内存泄漏。
那么shared_ptr是如何引起循环引用的呢?
先明确一个结论:shared_ptr智能指针管理的对象,只有在引用计数减为0的时候才会释放。
而循环引用发生的情况就是违反了上面所说的结论:引用计数不能递减到0,以至于对象不能释放。
shared_ptr的引用计数机制是这样的:
shared_ptr是多个智能指针指向相同的对象。引用计数就是标记同一块内存同时有多少个shared_ptr智能指针对象指向这个堆内存地址;通过赋值、拷贝等可以让引用计数增加;当一个指向某个内存的智能指针对象被销毁的时候,指向这个内存的所有智能指针的引用计数都减一;当只有一个智能指针对象指向一个内存地址(引用计数为1)也要被销毁的时候,该内存地址就会被系统自动回收。(其实是shared_ptr的析构函数所做的工作是:先将与该智能指针对象共享同一内存地址的所有智能指针对象的引用计数减一[其实,这个引用计数就是在该共享内存上面存着的,只要其中一个对象去修改,那么所有指向它的对象的引用计数就全都改了],减一之后判断是不是0,如果大于零,就不管别的只用销毁该对象即可;如果等于0,说明已经没有任何对象指向该内存空间,所以就要进行堆空间的回收)
举个栗子:
#include<iostream>
#include<memory>
using namespace std;
template<class T>
struct Node
{
Node(T data = T()) :_data(data), Pnext(NULL), Ppre(NULL)
{
}
~Node()
{
cout << "Call ~Node()" << endl;
}
shared_ptr<Node> Pnext;
shared_ptr<Node> Ppre;
T _data;
};
void testShared_Ptr()
{
shared_ptr<Node<int>>sp1(new Node<int>(1));
shared_ptr<Node<int>>sp2(new Node<int>(2));
cout << sp1.use_count() << endl;//1
cout << sp2.use_count() << endl;//1
sp1->Pnext = sp2;
sp2->Ppre = sp1;
cout << sp1.use_count() << endl;//2
cout << sp2.use_count() << endl;//2
}
int main()
{
testShared_Ptr();
cout<<"testShared_pte()函数运行结束"<<endl;
return 0;
}
我们在testShared_ptr函数里面开辟了两个结点,并分别交给两个Shared_ptr指针保管,他们的引用计数都是1
然后我们让sp1和结点node2里面的Ppre共同管理着结点node1(他们共用同一个引用计数),让sp2和node1里面的pNext共通过管理着node2(他们共用同一个引用计数);
此时两个智能指针的引用计数都变为了2,说明每个结点都有两个智能指针管理着,当我们继续往下执行时,会发现testShared_ptr()函数已经执行结束了,但是结点的析构函数并没有调用,我们开辟的结点,并没有被释放,运行结果如下图:
通过下图,我们来分析一下为什么这两个结点没有被释放:
为了解决shared_ptr引起的循环引用问题,我们引入一个weak_ptr指针,他不能单独使用,只能配合shared_ptr使用。
#include<iostream>
#include<memory>
using namespace std;
template<class T>
struct Node
{
Node(T data = T()) :_data(data)
{
}
~Node()
{
cout << "Call ~Node()" << endl;
}
weak_ptr<Node> Pnext;//使用weak_ptr
weak_ptr<Node> Ppre;
T _data;
};
void testShared_Ptr()
{
shared_ptr<Node<int>>sp1(new Node<int>(1));
shared_ptr<Node<int>>sp2(new Node<int>(2));
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
sp1->Pnext = sp2;
sp2->Ppre = sp1;
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
}
int main()
{
testShared_Ptr();
cout << "testShared_pte()函数运行结束"<<endl;
return 0;
}
weak_ptr不能控制它所指向对象的生存期。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,那么对象就会被释放。
根据这个weak_ptr的特性,可以完美地解决shared_ptr出现的循环引用问题。