记一次由于智能指针shared_ptr循环引用而产生的C++内存泄漏

自从 C++ 11 以来,boost 的智能指针就被加入了 C++ 新标准之中。其中,广为人知的 shared_ptr 被用的最多,以引用计数的方式来管理指针指向资源的生命周期。看起来有了智能指针后,C++ 程序再也不用担心内存泄漏了,就可以像 Java 一样愉快的创建堆上对象了。但事实并非如此,C++ 的智能指针和 Java 的引用实现原理上有本质的区别。在“循环引用”这个问题上,Java 可以很好地处理,而C++ 的智能指针 shared_ptr 在处理“循环引用”时便捉襟见肘。

1-一次由于循环引用产生的内存泄漏

鄙人最近在写实验代码,正好使用了 shared_ptr 来管理内存,但批量运行实验的时候发生了内存泄漏,导致我只跑了十几组实验程序便崩溃了。虽然可以将原来的 x86 改成 x64 扩大地址空间来解决这个问题,但终究不是长久之计,所以必须要解决之。抽取的代码如下:

class Node {
    Node(const Point &p);
    ...
private:
    shared_ptr<Node> neighbors;
};

这个代码写得很自然,如果是写 Java 程序自然没什么问题,但在使用引用计数的 C++ 智能指针上便内存泄漏了。其中 shared_ptr<Node> neighbors; 这个声明便是万恶之源,在所有的 Node 对象创建完成之后,每个 Node 对象遍历所有的 Node 的列表,在这个列表中找到自己的邻居,并将它们的智能指针加入到自己的邻居表中,在此产生了循环引用。

2-解决方法

在怀疑 Bug 是因为 shared_ptr 导致的内存泄漏后,我写了一个 Demo,测试了一下,发现也存在同样的问题,可以确定确实是这个原因产生的内存泄漏。解决这个内存泄漏的方法就是使用 weak_ptr 来指向其邻居,这样不会增加引用计数,在需要访问其指向变量的内存时使用 lock 方法获得 shared_ptr 对象来访问之。最后程序是这样的:

class Node {
public:
    Node(const Point &p);
    ...
private:
    weak_ptr<Node> neighbors_;
};

class Simulator {
public:
    ...
private:
    shared_ptr<Node> sens_nodes_;
};
3-循环引用为什么会导致内存泄漏

自智能指针的原理说起,一个智能指针在创建一个对象的时候初始化引用计数为 1,并把自己的指针指向创建的对象。但这个引用计数在何处?在智能指针内部?非也,这个计数是一个单独的对象来实现的,如图1,当另外一个智能指针指向这个对象的时候,便找到与这个对象对应的计数对象,并加一个引用,即 use_count++。这样多个智能指针对象便可以使用相同的引用计数。

引用计数原理

而如果产生相互引用的情况,类似以下代码:

class Person {
public:
    ...
    shared_ptr<Person> best_friend;
};
Person pa = make_shared<Person>();
Person pb = make_shared<Person>();
pa->best_friend = pb;
pb->best_friend = pa;

即使 pa 和 pb 均离开作用域析构掉了,内存也不会释放。为何?在 pa 离开作用域后,pa 这个只能指针对象实际上已经死亡了,但是 pa 所指向的对象并没有死亡,因为此时 pb 中的一个只能指针指向了这个对象。在 pb 离开作用域后,pb 这个智能指针实际上也完了,但是 pb 所指向的对象并没有死亡,因为 pa 曾经指向的对象中还有着一个指向它的指针。最后,由于两个对象保存着指向对方的指针,它们的引用计数均为 1,导致了内存无法释放。
循环引用

在理解了智能指针的实现原理和循环引用导致内存泄漏的原理后,使用 weak_ptr 这种不增加引用计数的弱指针便可以很好地解决这个问题。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值