智能指针循环引用问题


前言

        在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替代循环中的一个强引用,打破闭环,不影响正常资源持有逻辑。在使用智能指针时要注重各类指针的用法,应尽量避免出现循环引用问题。

        以上便是本文的全部内容,希望能帮到一些码友。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值