智能指针是否能够从容器中删除对其对象的其他引用?
Are smart pointers capable of removing other references to its object from containers?
我想实现这个:
- 对象 A 拥有一个对象 B(有一个指向它的指针)
- 当对象 A 被销毁时,对象 B 也被销毁。
- 对象 C 有 std::vector 个指向对象 B 的指针。
- 当对象 B 被销毁时,从对象 C 的向量中删除它的指针。
不同智能指针的组合是否可行?
让我们从角色的角度思考(现在忽略线程):
对象 A 拥有 B 的生命周期
对象C是B生命周期的观察者
你没有说 A 和 C 之间是否存在关系,所以我假设 A 在其构造函数中知道 C 的参与(让我们将 C 用作可配置工厂)。
B 的生命周期事件可以在 2 个地方创建观察 - B 的构造函数/析构函数(差 - 紧耦合)或中间工厂(更好 - 松耦合)。
所以:
#include <memory>
#include <algorithm>
struct B {
};
struct C {
std::shared_ptr<B> make_b() {
auto p = std::shared_ptr<B>(new B(), [this](B *p) {
this->remove_observer(p);
delete p;
});
add_observer(p.get());
return p;
}
private:
void add_observer(B *p) {
observers_.push_back(p);
}
void remove_observer(B *p) {
observers_.erase(std::remove(std::begin(observers_), std::end(observers_), p),
std::end(observers_));
}
std::vector<B *> observers_;
};
struct A {
A(C &factory)
: b_(factory.make_b()) {}
std::shared_ptr<B> b_;
};
int main() {
// note: factory must outlive a1 and a2
C factory;
A a1(factory);
A a2(factory);
}
请注意,虽然我使用了 shared_ptr
,但在这种情况下我可以轻松地使用 unique_ptr
。然而,我会在指针中将 A 与删除器类型耦合 - 所以我要么必须创建我自己的类型擦除删除器类型,要么将 A 更紧密地耦合到 C(我想避免)。
Object A owns an Object B (has a pointer to it)
When Object A is destroyed, Object B is destroyed too.
Object C has a std::vector of pointers to Object B-s.
When Object B is destroyed, remove its pointer from Object C's vector.
对象 A 的生命周期可以由 shared_ptr
管理。
它可以完全控制 B
:
的生命周期
struct A {
std::unique_ptr<B> b;
};
或
struct A {
B b;
};
我们将添加一个 observe_B
方法:
struct A {
std::unique_ptr<B> b;
B* observe_B() { return b.get(); }
B const* observe_B() const { return b.get(); }
};
我们将使其成为逻辑常量。对于我们有实际 B
的情况,我们只做 &
而不是 .get()
。所以我们不再关心 B
是如何分配的(指针或在 A 的主体中)。
现在我们有一个相对复杂的生命周期请求。明智地使用 shared_ptr
在这里可能是合适的。事实上,shared_from_this
:
struct A:std::enable_shared_from_this<A> {
std::unique_ptr<B> b;
B* observe_B() { return b.get(); }
B const* observe_B() const { return b.get(); }
std::shared_ptr<B const> get_shared_B() const {
return {shared_from_this(), observe_B()};
}
std::shared_ptr<B> get_shared_B() {
return {shared_from_this(), observe_B()};
}
};
这里我们使用"aliasing constructor"的共享指针指向return一个指向非共享对象的共享指针。它正是为此目的而设计的。我们使用 A
的共享生命周期语义,但将其应用于 B*
.
在C
中我们简单的存储了一个vector<weak_ptr>
.
struct C {
std::vector<std::weak_ptr<B>> m_Bs;
};
现在,当 A
消失时,weak_ptr
到 "contained" B
将失去最后一个强引用。当你 .lock()
它时,它现在失败了。
struct C {
std::vector<std::weak_ptr<B>> m_Bs;
void tidy_Bs() {
auto it = std::remove_if( begin(m_Bs), end(m_Bs), [](auto&& x)->bool{return !x.lock();});
m_Bs.erase(it, end(m_Bs));
}
};
tidy_Bs
删除 m_Bs
.
中的所有 "dangling" weak_ptr
到 B
为了迭代,我通常会这样做:
struct C {
std::vector<std::weak_ptr<B>> m_Bs;
void tidy_Bs() {
auto it = std::remove_if( begin(m_Bs), end(m_Bs), [](auto&& x)->bool{return !x.lock();});
m_Bs.erase(it, end(m_Bs));
}
template<class F>
void foreach_B(F&& f) {
tidy_Bs();
auto tmp = m_Bs;
for (auto ptr:m_Bs)
if (auto locked = ptr.lock())
f(*locked);
}
};
为 m_Bs
向量中每个仍然存在的 B
传递 f
一个 B&
。当它在那里时,它会清理死者。
我复制向量是因为在迭代时,有人可以去更改 m_Bs
的内容,为了稳健,我不能在发生这种情况时迭代 m_Bs
。
整个技术无需 A
由 shared_ptr
管理即可完成;但是 B
必须由 shared_ptr
管理。
请注意,如果 C
当前在 [=19= 上有一个 .lock()
,则 "normally" 导致 A
被销毁的操作实际上可能不会执行此操作] 包含在 A
中。实际上没有办法避免这种情况,除了使 C
崩溃。
我想实现这个:
- 对象 A 拥有一个对象 B(有一个指向它的指针)
- 当对象 A 被销毁时,对象 B 也被销毁。
- 对象 C 有 std::vector 个指向对象 B 的指针。
- 当对象 B 被销毁时,从对象 C 的向量中删除它的指针。
不同智能指针的组合是否可行?
让我们从角色的角度思考(现在忽略线程):
对象 A 拥有 B 的生命周期
对象C是B生命周期的观察者
你没有说 A 和 C 之间是否存在关系,所以我假设 A 在其构造函数中知道 C 的参与(让我们将 C 用作可配置工厂)。
B 的生命周期事件可以在 2 个地方创建观察 - B 的构造函数/析构函数(差 - 紧耦合)或中间工厂(更好 - 松耦合)。
所以:
#include <memory>
#include <algorithm>
struct B {
};
struct C {
std::shared_ptr<B> make_b() {
auto p = std::shared_ptr<B>(new B(), [this](B *p) {
this->remove_observer(p);
delete p;
});
add_observer(p.get());
return p;
}
private:
void add_observer(B *p) {
observers_.push_back(p);
}
void remove_observer(B *p) {
observers_.erase(std::remove(std::begin(observers_), std::end(observers_), p),
std::end(observers_));
}
std::vector<B *> observers_;
};
struct A {
A(C &factory)
: b_(factory.make_b()) {}
std::shared_ptr<B> b_;
};
int main() {
// note: factory must outlive a1 and a2
C factory;
A a1(factory);
A a2(factory);
}
请注意,虽然我使用了 shared_ptr
,但在这种情况下我可以轻松地使用 unique_ptr
。然而,我会在指针中将 A 与删除器类型耦合 - 所以我要么必须创建我自己的类型擦除删除器类型,要么将 A 更紧密地耦合到 C(我想避免)。
Object A owns an Object B (has a pointer to it)
When Object A is destroyed, Object B is destroyed too.
Object C has a std::vector of pointers to Object B-s.
When Object B is destroyed, remove its pointer from Object C's vector.
对象 A 的生命周期可以由 shared_ptr
管理。
它可以完全控制 B
:
struct A {
std::unique_ptr<B> b;
};
或
struct A {
B b;
};
我们将添加一个 observe_B
方法:
struct A {
std::unique_ptr<B> b;
B* observe_B() { return b.get(); }
B const* observe_B() const { return b.get(); }
};
我们将使其成为逻辑常量。对于我们有实际 B
的情况,我们只做 &
而不是 .get()
。所以我们不再关心 B
是如何分配的(指针或在 A 的主体中)。
现在我们有一个相对复杂的生命周期请求。明智地使用 shared_ptr
在这里可能是合适的。事实上,shared_from_this
:
struct A:std::enable_shared_from_this<A> {
std::unique_ptr<B> b;
B* observe_B() { return b.get(); }
B const* observe_B() const { return b.get(); }
std::shared_ptr<B const> get_shared_B() const {
return {shared_from_this(), observe_B()};
}
std::shared_ptr<B> get_shared_B() {
return {shared_from_this(), observe_B()};
}
};
这里我们使用"aliasing constructor"的共享指针指向return一个指向非共享对象的共享指针。它正是为此目的而设计的。我们使用 A
的共享生命周期语义,但将其应用于 B*
.
在C
中我们简单的存储了一个vector<weak_ptr>
.
struct C {
std::vector<std::weak_ptr<B>> m_Bs;
};
现在,当 A
消失时,weak_ptr
到 "contained" B
将失去最后一个强引用。当你 .lock()
它时,它现在失败了。
struct C {
std::vector<std::weak_ptr<B>> m_Bs;
void tidy_Bs() {
auto it = std::remove_if( begin(m_Bs), end(m_Bs), [](auto&& x)->bool{return !x.lock();});
m_Bs.erase(it, end(m_Bs));
}
};
tidy_Bs
删除 m_Bs
.
weak_ptr
到 B
为了迭代,我通常会这样做:
struct C {
std::vector<std::weak_ptr<B>> m_Bs;
void tidy_Bs() {
auto it = std::remove_if( begin(m_Bs), end(m_Bs), [](auto&& x)->bool{return !x.lock();});
m_Bs.erase(it, end(m_Bs));
}
template<class F>
void foreach_B(F&& f) {
tidy_Bs();
auto tmp = m_Bs;
for (auto ptr:m_Bs)
if (auto locked = ptr.lock())
f(*locked);
}
};
为 m_Bs
向量中每个仍然存在的 B
传递 f
一个 B&
。当它在那里时,它会清理死者。
我复制向量是因为在迭代时,有人可以去更改 m_Bs
的内容,为了稳健,我不能在发生这种情况时迭代 m_Bs
。
整个技术无需 A
由 shared_ptr
管理即可完成;但是 B
必须由 shared_ptr
管理。
请注意,如果 C
当前在 [=19= 上有一个 .lock()
,则 "normally" 导致 A
被销毁的操作实际上可能不会执行此操作] 包含在 A
中。实际上没有办法避免这种情况,除了使 C
崩溃。