shared_ptr 循环引用问题以及解决办法

栗子

#include <iostream>
#include <memory>

class CB;
class CA
{
public:
    CA() { std::cout << "CA() called! " << std::endl; }
    ~CA() { std::cout << "~CA() called! " << std::endl; }
    void set_ptr(std::shared_ptr<CB> &ptr) { m_ptr_b = ptr; }
    void b_use_count() { std::cout << "b use count : " << m_ptr_b.use_count() << std::endl; }
    void show() { std::cout << "this is class CA!" << std::endl; }
private:
    std::shared_ptr<CB> m_ptr_b;
};

class CB
{
public:
    CB() { std::cout << "CB() called! " << std::endl; }
    ~CB() { std::cout << "~CB() called! " << std::endl; }
    void set_ptr(std::shared_ptr<CA> &ptr) { m_ptr_a = ptr; }
    void a_use_count() { std::cout << "a use count : " << m_ptr_a.use_count() << std::endl; }
    void show() { std::cout << "this is class CB!" << std::endl; }
private:
    std::shared_ptr<CA> m_ptr_a;
};

void test_refer_to_each_other()
{
    std::shared_ptr<CA> ptr_a(new CA());
    std::shared_ptr<CB> ptr_b(new CB());

    std::cout << "a use count : " << ptr_a.use_count() << std::endl;
    std::cout << "b use count : " << ptr_b.use_count() << std::endl;

    ptr_a->set_ptr(ptr_b);
    ptr_b->set_ptr(ptr_a);

    std::cout << "a use count : " << ptr_a.use_count() << std::endl;
    std::cout << "b use count : " << ptr_b.use_count() << std::endl;
}

int main()
{
    test_refer_to_each_other();
    return 0;
}

结果

CA() called!
CB() called!
a use count : 1
b use count : 1
a use count : 2
b use count : 2

说明

上述结果说明,该 test_refer_to_each_other 执行完成之后,并没有释放掉 CA 和 CB 两个对象。因为起初定义完  ptr_a  和 ptr_b 时,只有 ① ③ 两条引用,然后调用函数 set_ptr 后又增加了 ② ④ 两条引用,当 test_refer_to_each_other 这个函数返回时,对象 ptr_a 和 ptr_b 被销毁,也就是 ① ③ 两条引用会被断开,但是 ② ④ 两条引用依然存在,每一个的引用计数都不为 0,结果就导致其指向的内部对象无法析构,造成内存泄漏。

解决办法

解决这种状况的办法就是将两个类中的一个成员变量改为 weak_ptr 对象。因为 weak_ptr 不会增加引用计数,使得引用形不成环,最后就可以正常的释放内部的对象,不会造成内存泄漏,比如将 CB 中的成员变量改为 weak_ptr 对象,代码如下:

class CB
{
public:
    CB() { cout << "CB() called! " << endl; }
    ~CB() { cout << "~CB() called! " << endl; }
    void set_ptr(shared_ptr<CA>& ptr) { m_ptr_a = ptr; }
    void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
    void show() { cout << "this is class CB!" << endl; }
private:
    weak_ptr<CA> m_ptr_a;
};

结果

CA() called!
CB() called!
a use count : 1
b use count : 1
a use count : 1
b use count : 2
~CA() called!
~CB() called!

通过这次结果可以看到,CA 和 CB 的对象都被正常的析构了,引用关系如下图所示,流程与上一例子相似,但是不同的是 ④ 这条引用是通过 weak_ptr 建立的,并不会增加引用计数,也就是说 CA 的对象只有一个引用计数,而 CB 的对象只有 2 个引用计数,当 test_refer_to_each_other 这个函数返回时,对象 ptr_a 和 ptr_b 被销毁,也就是①③两条引用会被断开,此时CA对象的引用计数会减为0,对象被销毁,其内部的 m_ptr_b 成员变量也会被析构,导致 CB 对象的引用计数会减为0,对象被销毁,进而解决了引用成环的问题。

 

(SAW:Game Over!)

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在 C++ 中,shared_ptr 常常被用来管理动态分配的资源。然而,当多个 shared_ptr 相互引用时,就会出现循环引用问题,导致内存泄漏。为了解决这个问题,C++11 引入了 weak_ptr。 weak_ptrshared_ptr 的一种扩展,它可以指向一个由 shared_ptr 管理的对象,但并不拥有该对象的所有权。weak_ptr 可以被用来解决 shared_ptr 循环引用问题。 当一个对象被多个 shared_ptr 共享时,每一个 shared_ptr 都会增加该对象的引用计数。如果其中一个 shared_ptr 被销毁时,该对象的引用计数会减少。但如果多个 shared_ptr 相互引用,就会导致循环引用问题。例如: ```c++ class B; class A { public: std::shared_ptr<B> b_ptr; }; class B { public: std::shared_ptr<A> a_ptr; }; int main() { std::shared_ptr<A> a(new A); std::shared_ptr<B> b(new B); a->b_ptr = b; b->a_ptr = a; return 0; } ``` 在上面的代码中,A 和 B 互相引用,它们的引用计数永远不会为 0,导致内存泄漏。为了解决这个问题,我们可以将其中一个 shared_ptr 改为 weak_ptr。例如: ```c++ class B; class A { public: std::weak_ptr<B> b_ptr; }; class B { public: std::shared_ptr<A> a_ptr; }; int main() { std::shared_ptr<A> a(new A); std::shared_ptr<B> b(new B); a->b_ptr = b; b->a_ptr = a; return 0; } ``` 在这个例子中,A 持有一个指向 B 的 weak_ptr,而 B 持有一个指向 A 的 shared_ptr。这样,当 A 或 B 中的任意一个 shared_ptr 被销毁时,它们所指向的对象的引用计数都会减少,从而解决了循环引用问题。 需要注意的是,当通过 weak_ptr 访问对象时,需要先将 weak_ptr 转换为 shared_ptr,否则无法访问对象。假设上面的例子中,我们需要访问 B 对象,可以这样做: ```c++ std::shared_ptr<B> b_ptr = a->b_ptr.lock(); if (b_ptr) { // 访问 B 对象的成员 } ``` 在上面的代码中,我们使用 lock() 方法将 weak_ptr 转换为 shared_ptr,如果转换成功,就可以访问 B 对象的成员了。 总之,weak_ptrshared_ptr 的一种扩展,可以用来解决 shared_ptr 循环引用问题。通过将其中一个 shared_ptr 改为 weak_ptr,可以防止循环引用导致的内存泄漏。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值