这个std::vector和std::shared_ptr内存泄漏是一个bug吗

Is this std::vector and std::shared_ptr memory leakage a bug?

本文关键字:std bug 一个 内存 vector shared 这个 ptr 泄漏      更新时间:2023-10-16

假设此类Foo:

struct Foo {
std::shared_ptr<int> data;
std::shared_ptr<std::vector<Foo>> foos;
};
  • 它有一个指向int 的指针

  • 它有一个指向该程序中存在的所有实例的指针(因此其中一个实例==*this)

让我们创建一个Foo的实例,并在向.foos:添加一些实例后查看其.data成员变量的use_count()

int main() {
Foo foo;
foo.data = std::make_shared<int>(5);
foo.foos = std::make_shared<std::vector<Foo>>();
foo.foos->resize(8);
for (auto & f : *foo.foos) {
f.data = foo.data;
f.foos = foo.foos;
}
std::cout << "use count: " << foo.data.use_count() << 'n';    
}

输出:

use count: 9

这是好的(1个foo+8个.foos)。然而,当main()返回时,似乎仍然会有98个指针指向.data!这可以通过将foo放入本地作用域并让一个额外的指针指向.data来观察该指针use_count()来证明:

int main() {
std::shared_ptr<int> ptr;
std::cout << "use count | before: " << ptr.use_count() << 'n';
{ //begin scope
Foo foo;
foo.data = std::make_shared<int>(5);
foo.foos = std::make_shared<std::vector<Foo>>();
foo.foos->resize(8);
for (auto & f : *foo.foos) {
f.data = foo.data;
f.foos = foo.foos;
}
ptr = foo.data;
std::cout << "use count | inside: " << ptr.use_count() << 'n';
} //end scope
std::cout << "use count | after: " << ptr.use_count() << 'n';
}

输出为:

use count | before: 0
use count | inside: 10
use count | after: 9

这不好。我将use count | after排除为1,因为foo及其所有成员应该在范围的末尾被解构。好吧,foo被明确地解构了(否则use_count | after将是10而不是9),但它的.foos矢量指针没有被解构。并且CCD_ 22只是CCD_ 23,因此与CCD_ 24完全无关。所有这些都可以通过为struct Foo提供一个析构函数来修复,reset()手动调用.foos->data指针:

#include <memory>
#include <iostream>
#include <vector>
struct Foo {
~Foo() {
for (auto& p : *foos) {
p.data.reset();
}
}
std::shared_ptr<int> data;
std::shared_ptr<std::vector<Foo>> foos;
};
int main() {
std::shared_ptr<int> ptr;
std::cout << "use count | before: " << ptr.use_count() << 'n';
{
Foo foo;
foo.data = std::make_shared<int>(5);
foo.foos = std::make_shared<std::vector<Foo>>();
foo.foos->resize(8);
for (auto & f : *foo.foos) {
f.data = foo.data;
f.foos = foo.foos;
}
ptr = foo.data;
std::cout << "use count | inside: " << ptr.use_count() << 'n';
}
std::cout << "use count | after: " << ptr.use_count() << 'n';
}

产生更好的输出:

use count | before: 0
use count | inside: 10
use count | after: 1

但奇怪的是,必须手动重置这些指针。为什么std::vectorstd::shared_ptr在这里不自动执行?是虫子吗?


我正在使用Visual Studio Community 2017 15.9.5版-感谢您的帮助!

问题是您有一个循环引用。

foo被破坏时,它会减少其shared_ptr引用计数,但这些计数不会达到零。

因此,即使std::shared_ptr<std::vector<Foo>>是"不可访问"的,它上仍然有指针。(注意:垃圾收集器使用"可访问性"来收集/释放指针)。

打破循环的常用方法是使用std::weak_ptr

您创建了一个循环依赖项:每个Foo都包含所有其他Foos的shared_ptrs(即共享所有权)。这意味着任何Foo都不会被销毁:要被销毁,use_count必须为零。但在进入析构函数之前,它不能为零,因为每隔一个Foo仍然保存一个引用。

这是共享所有权限制的经典案例——与一些观点相反,它并不能自动解决所有所有权问题。

我也会质疑每个Foo存储指向所有Foos的相同指针的意义。如果这是你想要做的,它应该只是static,但这听起来也不是一个好的设计。也许你可以详细说明你想要解决的实际问题(在一个新问题中)?