这是 std::vector 和 std::shared_ptr 内存泄漏的错误吗?
Posted
技术标签:
【中文标题】这是 std::vector 和 std::shared_ptr 内存泄漏的错误吗?【英文标题】:Is this std::vector and std::shared_ptr memory leakage a bug? 【发布时间】:2019-01-30 10:02:06 【问题描述】:假设这个类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()
返回时,还是会有 9 8个指针指向.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()
s 手动指向 .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 - 感谢您的帮助!
【问题讨论】:
不明白,在ent ptr 处仍然指向foos 数据并且使用count 为1?那么……有什么问题? 我明白了……对不起…… 当程序超出 main() 范围时,指针将被释放。 没有错误,这是预期的行为。您必须手动重置那些嵌套的Foo
s,因为您正在创建循环引用(向量中的Foo
s 对其所在的向量有一个 shared_ptr,从而使它们保持活动状态)并且 shared_ptr 并非旨在处理它。
离题:解构 == 销毁...
【参考方案1】:
问题是你有一个循环引用。
当foo
被销毁时,它会减少其shared_ptr
的引用计数,但不会达到零。
所以即使 std::shared_ptr<std::vector<Foo>>
是 "inaccessible" ,它上面仍然有指针。 (注意:垃圾收集器使用“可访问性”来收集/释放指针)。
打破循环的常用方法是使用std::weak_ptr
。
【讨论】:
也许要进一步澄清:foo.foos
的每个条目都是一个Foo
,它保留对foo.data
和foo.foos
的引用。此时所有指向foo.foos
的指针都可以互换,删除一个不会影响其余的。
谢谢,这看起来像我正在寻找的东西。尽管我还不知道如何在此示例代码中使用weak_ptr
,但我接受了答案。它似乎没有 ->
这样的运算符。
在使用关联的std::shared_ptr
之前,您必须先lock 它(检查nullptr
)。
好的,我明白了。但是由于weak_ptr
想要设置为现有的shared_ptr
,我将不得不将shared_ptr<vector<Foo>>
和 weak_ptr<vector<Foo>>
都放在结构中,对吗?
您不必两者都放。很难说正确的方法,因为您的样本被简化了。 std::vector<std::weak_ptr<Foo>>
似乎是一种选择。【参考方案2】:
您创建了一个循环依赖项:每个Foo
包含所有其他Foo
s 的shared_ptr
s(即共享所有权)。这意味着Foo
永远不会被破坏:要被破坏,use_count
必须为零。但在进入析构函数之前它不能为零,因为其他每个 Foo
仍然持有一个引用。
这是共享所有权限制的经典案例 - 与某些信念相反,它不会自动解决您的所有所有权问题。
我也会质疑每个Foo
存储指向所有Foo
s 的相同指针的意义。如果这就是你想要做的,它应该只是static
,但这听起来也不是好的设计。也许您可以详细说明您想要解决的实际问题(在一个新问题中)?
【讨论】:
所有权根据定义不是循环的。如果您最终获得循环所有权,则会出现语义错误。以上是关于这是 std::vector 和 std::shared_ptr 内存泄漏的错误吗?的主要内容,如果未能解决你的问题,请参考以下文章
std::vector::resize() 与 std::vector::reserve()