std::vector 和 std::shared_ptr 内存泄漏是一个错误吗?
Is this std::vector and std::shared_ptr memory leakage a bug?
假设这个 class 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()
returns的时候,还是会有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
向量指针没有被解构。 ptr
只是一个 std::shared_ptr<int>
,因此与 struct Foo
完全无关。所有这些都可以通过提供 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::vector
或 std::shared_ptr
不自动执行此操作?这是一个错误吗?
我正在使用 Visual Studio Community 2017 版本 15.9.5 - 感谢您的帮助!
问题是你有一个循环引用。
当 foo
被销毁时,它会减少其 shared_ptr
的 引用计数 ,但不会达到零。
所以即使 std::shared_ptr<std::vector<Foo>>
是 "inaccessible" ,上面仍然有指针。 (注意:垃圾收集器使用 "accessibility" 到 collect/free 指针)。
通常打破循环的方法是使用std::weak_ptr
.
您创建了一个循环依赖:每个 Foo
包含所有其他 Foo
的 shared_ptr
(即股份所有权)。这意味着 Foo
永远不会被破坏:要被破坏,use_count
必须为零。但是在进入析构函数之前它不能为零,因为每隔 Foo
仍然持有一个引用。
这是共享所有权限制的典型案例 - 与某些信念相反,它不会自动解决您所有的所有权问题。
我还会质疑每个 Foo
存储指向所有 Foo
的相同指针的意义。如果那是你想要做的,它应该只是 static
,但这听起来也不是好的设计。也许你可以详细说明你想要解决的实际问题(在一个新问题中)?
假设这个 class 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()
returns的时候,还是会有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
向量指针没有被解构。 ptr
只是一个 std::shared_ptr<int>
,因此与 struct Foo
完全无关。所有这些都可以通过提供 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::vector
或 std::shared_ptr
不自动执行此操作?这是一个错误吗?
我正在使用 Visual Studio Community 2017 版本 15.9.5 - 感谢您的帮助!
问题是你有一个循环引用。
当 foo
被销毁时,它会减少其 shared_ptr
的 引用计数 ,但不会达到零。
所以即使 std::shared_ptr<std::vector<Foo>>
是 "inaccessible" ,上面仍然有指针。 (注意:垃圾收集器使用 "accessibility" 到 collect/free 指针)。
通常打破循环的方法是使用std::weak_ptr
.
您创建了一个循环依赖:每个 Foo
包含所有其他 Foo
的 shared_ptr
(即股份所有权)。这意味着 Foo
永远不会被破坏:要被破坏,use_count
必须为零。但是在进入析构函数之前它不能为零,因为每隔 Foo
仍然持有一个引用。
这是共享所有权限制的典型案例 - 与某些信念相反,它不会自动解决您所有的所有权问题。
我还会质疑每个 Foo
存储指向所有 Foo
的相同指针的意义。如果那是你想要做的,它应该只是 static
,但这听起来也不是好的设计。也许你可以详细说明你想要解决的实际问题(在一个新问题中)?