如何优雅地使用智能指针在 C++ 中为复杂的生命周期建模?
How do I elegantly use smart pointers to model complex lifetimes in C++?
假设我有一组 Foo
个对象,每个 Foo
拥有一个或多个 Bar
个对象。我的界面的用户可以删除特定的 Foo
或 Bar
;当删除 Foo
时,它拥有的所有 Bar
也会被删除。到目前为止,我只需要给每个 Foo
个 unique_ptr<Bar>
的集合来自动管理这个模型。但是,我有一个额外的要求:一旦 Foo
没有任何 Bar
,它也应该被删除。
当然,我可以只编写明确处理此问题的代码,但我想知道是否有更惯用的方法来实现同样的事情。这听起来很像 shared_ptr
,但又不完全...
由于从 Foo
中删除所有 Bar
应该删除 Foo
,这听起来确实像是您需要从 [=11] 中删除一些 shared_ptr
=]s 到他们的 Foo
.
但是,该模型会将 Foo
的生命周期置于其 Bar
的手中:您将无法直接删除 Foo
,而是您必须找到它的所有 Bar
并删除 这些 。
Foo
会保留 Bar*
s 而不是 unique_ptr<Bar>
s,因为它不会在 Bar
s 之前死亡。但是你必须把 Bar
的所有权给 某人 ...
您最终可能会得到另一个对象,该对象包含与每个 Foo
对应的 unique_ptr<Bar>
的集合,但是您必须保持所有这些同步,因为 Bar
来来去去。这与您试图避免的簿记类型相同,但结果更大、更复杂、更脆弱,以内存泄漏和流氓指针作为失败案例。
因此,我建议您坚持第一个 unique_ptr
驱动的想法,而不是所有这些。第一次实施可能如下所示:
struct Foo {
private:
friend void remove(std::unique_ptr<Foo> &foo, Bar const *bar);
// Removes the bar from this Foo.
// Returns true iff the Foo is now empty and should be deleted.
bool remove(Bar const *bar) {
auto i = std::find_if(begin(_bars), end(_bars), [&](auto const &p) {
return p.get() == bar;
});
assert(i != end(_bars));
_bars.erase(i);
return _bars.empty();
}
std::vector<std::unique_ptr<Bar>> _bars;
};
// Removes the bar from the Foo.
// Deletes the Foo if it becomes empty.
void remove(std::unique_ptr<Foo> &foo, Bar const *bar) {
if(foo->remove(bar))
foo.reset();
}
没有惯用的解决方案。在聚合内部使用 shared_ptr 和 unique_ptr,并使用 weak_ptr 作为聚合的锚点。这是我对你的问题的详尽示例:
#include <memory>
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
using std::shared_ptr;
using std::unique_ptr;
using std::weak_ptr;
using std::make_shared;
using std::make_unique;
struct Foo;
struct Bar
{ int b;
shared_ptr<Foo> f;
Bar(int b, shared_ptr<Foo> f): b(b), f(f) { cout << "Constructor B with: " <<b << std::endl; }
~Bar() { cout << "Destructor B with: " <<b << endl; }
};
struct Foo
{
Foo() { cout << "Constructor Foo" << endl; }
~Foo() { cout << "Destructor Foo" << endl; }
void clear() { vb.clear(); }
vector<unique_ptr<Bar>> vb;
};
int main(int argc, char* argv[])
{ weak_ptr<Foo> wf; // Anchor to Aggregate
{ // Construction of Aggregate
vector<shared_ptr<Bar>> vb2;
shared_ptr<Foo> f =std::make_shared<Foo>();
f->vb.emplace_back(make_unique<Bar>(1, f));
f->vb.emplace_back(make_unique<Bar>(2, f));
wf =f;
}
shared_ptr<Foo> f3 =wf.lock();
if (f3)
{
if (argv[1][0] =='f')
{
cout <<"Destroy Foo" <<endl;
f3->clear();
}
if (argv[1][0] =='b')
{
cout <<"Destroy Bar" <<endl;
f3->vb[0].reset();
f3->vb[1].reset();
}
}
}
使用参数 'f' 或 'b' 调用程序,输出将是:
Constructor Foo
Constructor B with: 1
Constructor B with: 2
Destroy ???
Destructor B with: 1
Destructor B with: 2
Destructor Foo
假设我有一组 Foo
个对象,每个 Foo
拥有一个或多个 Bar
个对象。我的界面的用户可以删除特定的 Foo
或 Bar
;当删除 Foo
时,它拥有的所有 Bar
也会被删除。到目前为止,我只需要给每个 Foo
个 unique_ptr<Bar>
的集合来自动管理这个模型。但是,我有一个额外的要求:一旦 Foo
没有任何 Bar
,它也应该被删除。
当然,我可以只编写明确处理此问题的代码,但我想知道是否有更惯用的方法来实现同样的事情。这听起来很像 shared_ptr
,但又不完全...
由于从 Foo
中删除所有 Bar
应该删除 Foo
,这听起来确实像是您需要从 [=11] 中删除一些 shared_ptr
=]s 到他们的 Foo
.
但是,该模型会将 Foo
的生命周期置于其 Bar
的手中:您将无法直接删除 Foo
,而是您必须找到它的所有 Bar
并删除 这些 。
Foo
会保留 Bar*
s 而不是 unique_ptr<Bar>
s,因为它不会在 Bar
s 之前死亡。但是你必须把 Bar
的所有权给 某人 ...
您最终可能会得到另一个对象,该对象包含与每个 Foo
对应的 unique_ptr<Bar>
的集合,但是您必须保持所有这些同步,因为 Bar
来来去去。这与您试图避免的簿记类型相同,但结果更大、更复杂、更脆弱,以内存泄漏和流氓指针作为失败案例。
因此,我建议您坚持第一个 unique_ptr
驱动的想法,而不是所有这些。第一次实施可能如下所示:
struct Foo {
private:
friend void remove(std::unique_ptr<Foo> &foo, Bar const *bar);
// Removes the bar from this Foo.
// Returns true iff the Foo is now empty and should be deleted.
bool remove(Bar const *bar) {
auto i = std::find_if(begin(_bars), end(_bars), [&](auto const &p) {
return p.get() == bar;
});
assert(i != end(_bars));
_bars.erase(i);
return _bars.empty();
}
std::vector<std::unique_ptr<Bar>> _bars;
};
// Removes the bar from the Foo.
// Deletes the Foo if it becomes empty.
void remove(std::unique_ptr<Foo> &foo, Bar const *bar) {
if(foo->remove(bar))
foo.reset();
}
没有惯用的解决方案。在聚合内部使用 shared_ptr 和 unique_ptr,并使用 weak_ptr 作为聚合的锚点。这是我对你的问题的详尽示例:
#include <memory>
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
using std::shared_ptr;
using std::unique_ptr;
using std::weak_ptr;
using std::make_shared;
using std::make_unique;
struct Foo;
struct Bar
{ int b;
shared_ptr<Foo> f;
Bar(int b, shared_ptr<Foo> f): b(b), f(f) { cout << "Constructor B with: " <<b << std::endl; }
~Bar() { cout << "Destructor B with: " <<b << endl; }
};
struct Foo
{
Foo() { cout << "Constructor Foo" << endl; }
~Foo() { cout << "Destructor Foo" << endl; }
void clear() { vb.clear(); }
vector<unique_ptr<Bar>> vb;
};
int main(int argc, char* argv[])
{ weak_ptr<Foo> wf; // Anchor to Aggregate
{ // Construction of Aggregate
vector<shared_ptr<Bar>> vb2;
shared_ptr<Foo> f =std::make_shared<Foo>();
f->vb.emplace_back(make_unique<Bar>(1, f));
f->vb.emplace_back(make_unique<Bar>(2, f));
wf =f;
}
shared_ptr<Foo> f3 =wf.lock();
if (f3)
{
if (argv[1][0] =='f')
{
cout <<"Destroy Foo" <<endl;
f3->clear();
}
if (argv[1][0] =='b')
{
cout <<"Destroy Bar" <<endl;
f3->vb[0].reset();
f3->vb[1].reset();
}
}
}
使用参数 'f' 或 'b' 调用程序,输出将是:
Constructor Foo
Constructor B with: 1
Constructor B with: 2
Destroy ???
Destructor B with: 1
Destructor B with: 2
Destructor Foo