前言
在c++11版本中引进的三种智能指针shared_ptr、unique_ptr、weak_ptr。极大的解决了手动内存管理的问题,但是在使用中也容易出现一些问题,本文讲述了循环引用问题及其解决方法。
一、什么是智能指针循环引用问题?
在使用shared_ptr可能出现的一种内存泄漏场景,根源是两个或多个shared_ptr互相持有对方的引用,导致引用计数无法归零,最终资源无法释放。
代码示例
#include<iostream>
#include<memory>
using namespace std;
// 前置声明:告知编译器A和B是类类型,便于后续互相引用
class A;
class B;
class A
{
public:
// A类中持有指向B类对象的shared_ptr智能指针
shared_ptr<B> bptr;
// A类的析构函数:用于观察对象是否被销毁
~A()
{
cout << " 类A被析构" << endl;
}
};
class B
{
public:
// B类中持有指向A类对象的shared_ptr智能指针
shared_ptr<A> aptr;
// B类的析构函数:用于观察对象是否被销毁
~B()
{
cout << " 类B被析构" << endl;
}
};
void test()
{
// 创建A类对象,由shared_ptr智能指针ap管理,此时A对象的引用计数为1
shared_ptr<A> ap(new A);
// 创建B类对象,由shared_ptr智能指针bp管理,此时B对象的引用计数为1
shared_ptr<B> bp(new B);
// 输出初始引用计数:A和B的引用计数均为1(仅被各自的智能指针持有)
cout << "A 的引用计数" << ap.use_count() << endl;
cout << "B 的引用计数" << bp.use_count() << endl;
// A对象的bptr成员指向B对象(bp),此时B对象的引用计数+1(变为2)
ap->bptr = bp;
// B对象的aptr成员指向A对象(ap),此时A对象的引用计数+1(变为2)
bp->aptr = ap;
// 输出关联后的引用计数:A和B的引用计数均为2(被各自的智能指针和对方的成员指针持有)
cout << "A 的引用计数" << ap.use_count() << endl;
cout << "B 的引用计数" << bp.use_count() << endl;
}
// test()函数结束后,局部智能指针ap和bp被销毁:
// - ap销毁时,A对象的引用计数从2减为1(仍被B对象的aptr持有)
// - bp销毁时,B对象的引用计数从2减为1(仍被A对象的bptr持有)
// 由于A和B对象的引用计数始终不为0,它们的析构函数不会被调用,导致内存泄漏
int main()
{
test();
// 程序输出:
// A 的引用计数1
// B 的引用计数1
// A 的引用计数2
// B 的引用计数2
// (注意:此处没有输出"A被析构"和"B被析构",证明内存泄漏)
return 0;
}
图例演示

创建A、B类对象,由shared_ptr智能指针ap、bp管理,此时A、B对象的引用计数为1
A对象的bptr成员指向B对象(bp),此时B对象的引用计数+1变为2,B对象的aptr成员指向A对象(ap),此时A对象的引用计数+1变为2。

test()函数结束后,局部智能指针ap和bp被销毁:
ap销毁时,A对象的引用计数从2减为1(仍被B对象的aptr持有)bp销毁时,B对象的引用计数从2减为1(仍被A对象的bptr持有)由于A和B对象的引用计数始终不为0,它们的析构函数不会被调用,导致内存泄漏。
二、怎么解决循环引用问题?
其实这个问题很好解决,导致循环引用的问题关键就是互相依赖,导致彼此的引用计数都不能为0,析构函数无法正常调用,导致资源无法释放。
weak_ptr指针的特性是不增加引用计数,仅用于观察资源,利用这一特性就可以很好的解决循环引用问题。
代码示例
#include<iostream>
#include<memory>
using namespace std;
// 前置声明:告知编译器A和B是类类型,支持后续互相引用
class A;
class B;
class A
{
public:
// A类持有指向B类对象的shared_ptr(强引用,会增加引用计数)
shared_ptr<B> bptr;
~A()
{
cout << " 类A被析构" << endl;
}
};
class B
{
public:
// B类持有指向A类对象的weak_ptr(弱引用,不增加引用计数)
weak_ptr<A> aptr;
~B()
{
cout << " 类B被析构" << endl;
}
};
void test()
{
// 创建A类对象,由shared_ptr ap管理,A的引用计数初始为1
shared_ptr<A> ap(new A);
// 创建B类对象,由shared_ptr bp管理,B的引用计数初始为1
shared_ptr<B> bp(new B);
// 输出初始引用计数:A和B均只被自身的智能指针持有,计数为1
cout << "A 的引用计数" << ap.use_count() << endl;
cout << "B 的引用计数" << bp.use_count() << endl;
// A对象的bptr指向B对象(bp):B的引用计数+1(变为2)
ap->bptr = bp;
// B对象的aptr指向A对象(ap):weak_ptr不增加引用计数,A的计数仍为1
bp->aptr = ap;
// 输出关联后的引用计数:
// A的计数仍为1(仅被ap持有,B的aptr是弱引用不计数)
// B的计数为2(被bp和A的bptr共同持有)
cout << "A 的引用计数" << ap.use_count() << endl;
cout << "B 的引用计数" << bp.use_count() << endl;
}
// test()函数结束时,局部智能指针ap和bp销毁:
// 1. ap销毁:A的引用计数从1减为0 → A对象被析构
// 2. A对象析构后,其成员bptr(指向B的shared_ptr)也被销毁 → B的引用计数从2减为1
// 3. bp销毁:B的引用计数从1减为0 → B对象被析构
// (整个过程无循环引用,资源正常释放)
int main()
{
test();
// 程序输出:
// A 的引用计数1
// B 的引用计数1
// A 的引用计数1
// B 的引用计数2
// 类A被析构
// 类B被析构
// (析构函数正常调用,证明内存未泄漏)
return 0;
}
图例演示

创建A、B类对象,由shared_ptr智能指针ap、bp管理,此时A、B对象的引用计数为1。
A对象的bptr指向B对象(bp):B的引用计数+1变为2,B对象的aptr指向A对象(ap):weak_ptr不增加引用计数,A的计数仍为1

当类A被释放时,ap和bptr均被销毁,此时A的引用计数变为0,当类B被释放时,bp被销毁,此时B的引用计数也变为0,资源被回收。
总结
shared_ptr互相强引用导致计数 “锁死”,是循环引用的根源。用weak_ptr替代循环中的一个强引用,打破闭环,不影响正常资源持有逻辑。在使用智能指针时要注重各类指针的用法,应尽量避免出现循环引用问题。
以上便是本文的全部内容,希望能帮到一些码友。
954

被折叠的 条评论
为什么被折叠?



