【C++11】C++智能指针循环引用问题分析
C++11中引入了三种智能指针,分别是shared_ptr、weak_ptr和unique_ptr
智能指针的作用
智能指针可以帮助我们管理动态分配的堆内存,减少内存泄漏的可能性
手动管理堆内存有引起内存泄漏的可能,比如这段代码
try {
int* p = new int;
// Do something
delete p;
} catch(...) {
// Catch exception
}
如果在执行Do something的时候发生了异常,那么程序就会直接跳到catch语句捕获异常,delete p这句代码不会被执行,发生了内存泄漏
我们把上面的程序改成
try {
shared_ptr<int> p(new int);
// Do something
} catch(...) {
// Catch exception
}
当执行Do something的时候发生了异常,那么try块中的栈对象都会被析构。因此代码中p的析构函数会被调用,引用计数从1变成0,通过new分配的堆内存被释放,这样就避免了内存泄漏的问题
循环引用问题
虽然智能指针会减少内存泄漏的可能性,但是如果使用智能指针的方式不对,一样会造成内存泄漏。比较典型的情况是循环引用问题,比如这段代码
class B; // 前置声明
class A {
public:
shared_ptr<B> ptr;
};
class B {
public:
shared_ptr<A> ptr;
};
int main()
{
while(true) {
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa -> ptr = pb;
pb -> ptr = pa;
}
return 0;
}
这个程序中智能指针的引用情况如下图
上图中,class A和class B的对象各自被两个智能指针管理,也就是pa和pb引用计数都为2,为什么是2?
因为shared_ptr是一组指针指向一个实例,所以有几个shared_ptr指向类A,则类A就有几次引用。,class B对象同理。
在这种情况下,在main函数中一个while循环结束的时候,pa和pb的析构函数被调用,但是class A对象和class B对象仍然被一个智能指针管理,pa和pb引用计数变成1,于是这两个对象的内存无法被释放,造成内存泄漏,如下图所示
解决方法weak_ptr
std::weak_ptr
避免shared_ptr内存泄漏的利器。👈
smart pointer 三兄弟性格各异。unque_ptr是独来独往,shared_ptr是左拥右抱,而weak_ptr生来就不是为了单打独斗,了解之后你会发现他总是和shared_ptr出双入对。
weak_ptr为什么存在?
特性:
不可使用* 和 ->访问对象
被赋值,不会引起shared_ptr内部计数器值变化(我猜是它严格上来说不具备指针的能力---访问对象)
所以,我们就可以用weak_ptr替代shared_ptr, 看👇例子。
#include <iostream>
#include <memory>
using namespace std;
class B;
class A
{
public:
A()
{
cout << "A's constructor ..." << endl;
}
~A()
{
cout << "A's destructor ..." << endl;
}
std::weak_ptr<B> weak_b;
};
class B
{
public:
B()
{
cout << "B's constructor ..." << endl;
}
~B()
{
cout << "B's destructor ..." << endl;
}
std::weak_ptr<A> weak_a;
};
int main()
{
std::shared_ptr<A> aa = make_shared<A>(); //aa->object A aa计数器 1
std::shared_ptr<B> bb = make_shared<B>(); //bb->object B bb计数器 1
aa->weak_b = bb; //计数器还是1哦
bb->weak_a = aa; //计数器还是1哦
return 0;
}
object A & B都被成功析构(销毁)了。那么循环引用导致shared_ptr发生内存泄漏的问题迎刃而解!
原因是:weak_ptr不会增加shared_ptr的计数器,从而离开mian函数作用域时,shared_ptr aa & bb 计数器都 -1 ,成为0, 具备销毁条件,调用析构函数销毁自己和所指对象
至此,我们终于可以说weak_ptr具备避免内存泄漏的功能了!!!