使用std::vector和std::shared_ptr出现疑似内存泄漏,是否为bug?
首先可以明确的是,这不是Visual Studio 2017的bug,而是典型的std::shared_ptr循环引用陷阱,和std::vector本身无关。让我们一步步拆解问题:
为什么离开作用域后data的use_count保持为9?
你的结构体Foo存在一个隐形的循环引用链:
- 局部
Foo实例持有std::shared_ptr<std::vector<Foo>> foos,这个指针指向包含8个Foo拷贝的vector; - vector中的每个
Foo拷贝,又都持有同一个foos指针(指向该vector); - 同时,所有
Foo实例(局部实例+vector中的8个拷贝)的data都指向同一个int对象,所以初始use_count是9。
当局部Foo实例离开作用域时:
- 它的
data和foos会被销毁,data的引用计数减1(理论上变成8),foos的引用计数也减1; - 但vector中的8个
Foo拷贝还各自持有foos指针,这意味着vector的shared_ptr引用计数还有8,远大于0——vector因此不会被销毁,里面的8个Foo拷贝也会一直存在,它们的data指针持续引用着int对象,所以data的use_count始终保持在较高数值(你看到的9可能是代码细节上的差异,比如vector中包含了局部实例本身)。
简单说:vector和它内部的Foo实例互相持有shared_ptr,形成了闭环,导致双方的引用计数都无法降到0,内存无法自动释放。
为什么手动添加析构函数重置data后恢复正常?
当你在Foo的析构函数中主动重置vector里所有元素的data指针时:
- 这一步直接减少了
data的引用计数(一次性减8); - 同时,当局部
Foo的foos被销毁后,vector的引用计数降到0,vector被销毁,里面的Foo拷贝也会被析构,此时data的引用计数就会降到预期的1(如果还有其他残留引用)或0(完全释放)。
更优雅的解决方案:用std::weak_ptr打破循环
手动重置指针的方式不够优雅,也容易遗漏。正确的做法是把Foo中的foos类型从std::shared_ptr<std::vector<Foo>>改成std::weak_ptr<std::vector<Foo>>:
struct Foo { std::shared_ptr<int> data; std::weak_ptr<std::vector<Foo>> foos; };
std::weak_ptr不会增加所指向对象的引用计数,它只是一个“观察者”指针。这样一来:
- 局部
Foo的shared_ptr<std::vector<Foo>>是唯一持有vector的强引用; - 当局部
Foo离开作用域,foos的强引用被销毁,vector的引用计数降到0,vector和内部的所有Foo拷贝都会被自动销毁; - 所有
data指针的引用计数也会随之降到0,int对象被正确释放,不会出现引用计数残留的问题。
内容的提问来源于stack exchange,提问作者Stack Danny
相关产品推荐
相关产品推荐

