我已经看到了Observer设计模式的实现,其中Observer负责多个Subject。大多数这些实现都使用std::vector<Subject*>
来跟踪主题。
我是否可以使用std::unordered_set<weak_ptr<Subject>>
做类似的事情?
我想使用unordered_set
的原因是我不需要重复,而且我不需要有序的容器。根据我的理解,unordered_set
是这种情况下的方法。另外,我使用weak_ptr
的原因是它应该更安全吗?
如果您不同意,请留下答案,说明我应该使用哪个容器。如果我确实使用了unordered_set
,我必须为weak_ptr
声明一个哈希函数,但这可以通过使用内部指针的哈希函数来实现,用subjects.lock().get()
获得?
答案 0 :(得分:1)
首先,在我的回答中,我将使用Subject作为向注册观察者发送消息的人,因为这是这两个术语的常见用法。
我是否可以使用
std::unordered_set<weak_ptr<Observer>>
做类似的事情?
有可能。但是请记住,weak_ptr保存的对象可以被释放,weak_ptr需要在访问底层对象之前被转换为shared_ptr。这样做是因为在处理对象时不会释放对象。
使用std :: unordered_set&gt;是否可以做类似的事情?代替?
如果您需要强制执行唯一性,则unordered_set对我来说是一个不错的选择。如果你不需要,那么矢量就是更简单的解决方案。有人会告诉你,unique_set比矢量要慢,需要更多的内存,但是除非你需要观察者或其中数千个频率的高频率注册,否则你不会注意到差异。
关于弱指针,它为您提供了在注册时释放观察者的灵活性,所以应该没问题。如果您来自Java等内存管理语言,则此行为可能会出乎意料。如果你想在他们在你的主题中注册时保持它们存在,你可以使用shared_pointer。
我必须为weak_ptr声明一个哈希函数,但是这可以通过使用使用observer.lock()得到的指针内部的哈希函数来实现.get()?
创建哈希函数时要小心,我不建议您使用对象的指针作为哈希函数,特别是如果可以复制/移动主题。相反,您可以在使用计数器创建时为每个主题创建唯一标识符,并记住相应地编写复制/移动构造函数和运算符。
如果你不能写一个识别哈希函数,那么你就不应该使用unique_set,因为你失去了它带来的好处。
作为一个脚注,对象容器的美妙之处在于您可以根据自己的需要调整它们,如果它能够满足您的需求,那么每个解决方案都是正确的解决方案。
答案 1 :(得分:0)
在Observer模式中,Observer订阅有关Subject的更改的通知。主体负责在其可观察状态发生变化时更新所有已订阅的观察者。为了工作,观察者不需要跟踪主题。相反,主体必须跟踪所有订阅的观察者。
可以在此处找到Observer模式的一个很好的解释:https://sourcemaking.com/design_patterns/observer
代码大纲:
class Subject;
class Observer
{
public:
// when notified about a change, the Observer
// knows which Subject changed, because of the parameter s
virtual void subjectChanged(Subject* s)=0;
};
class Subject
{
private:
int m_internalState;
std::set<Observer*> m_observers;
public:
void subscribe(Observer* o)
{
m_observers.insert(o);
}
void unsubscribe(Observer* o);
{
m_observers.erase(o);
}
void setInternalState(int state)
{
auto end=m_observers.end();
for(auto it=m_observers.begin(); it != end; ++it)
it->subjectChanged(this);
}
};
在大多数情况下,选择哪种精确的集合类型来存储观察者并不重要,因为观察者很少。但是,选择set-type有一个好处,即每个Observer只会收到一个通知。使用vector
,如果(出于某种原因)多次订阅,则可能会发生相同的观察者收到有关同一更改的多个通知。
答案 2 :(得分:0)
真的没有一个正确的&#39;回答容器的选择;从性能的角度来看,这取决于你的目标。而且表现是否真的对此非常重要。
它还取决于记忆效率。如果您只有一些这些unordered_set对象并且需要非常快速的查找,那么它可能是一个不错的选择。由于它是一个哈希表,因此每个unordered_set对象将使用相当大量的内存。
如果你有很多物品相当少的unordered_set对象,那么在内存预算方面可能会有点贵。如果你需要快速插入和删除,那么在这种情况下std :: set可能会更好。但是,如果集合只包含少量项目,那么由于处理器缓存而导致std :: vector的线性搜索实际上查找速度可能会更快(即与std :: set相比,矢量元素的引用位置更好 - 这可能导致更多元素位于同一缓存行上。 vector的内存使用率将低于std :: set或std :: unordered_set。
如果由于某种原因需要快速查找特定对象并使用std :: vector并且通常具有适度数量的元素,那么您可以按排序顺序将项目插入到向量中。然后,您可以使用std :: lower_bound执行O(log n)二进制搜索查找。但是,插入和删除元素的成本可能很高。
在大多数情况下,我可能只是去std:vector - 你通常只有很少的观察者,所以也可以保持内存使用更紧密。 如果在其他地方使用shared_ptr,那么使用weak_ptr肯定是一个不错的选择。
答案 3 :(得分:0)
我真的认为使用std::unordered_set
有点过分杀人
这个观察者模式是什么?当一个事件或状态发生变化时,迭代一系列状态检查器,如果状态无效或任何类型的特殊状态,则让它们做某事。
这就是说,你想用带有覆盖虚函数的对象迭代一个数组并调用它。为什么set
给我们带来任何好处?
另外,我没有得到weak_ptr
的想法 - 观察者的所有者是拥有它们的数组。该数组的所有者是主题。
现在所有人都说了,我会选择std::vector<std::unique_ptr<Observer>>
。
编辑:
使用C ++ 11,我甚至可以使用std::vector<std::function<void(Subject&)>>
并避免继承+覆盖的样板。
答案 4 :(得分:-2)
最简单的方法是使用已经为您实现此功能的boost::signals2
滚动所有签名。您的方法的根本问题在于,实现与特定主题和观察者的特定签名相关联,与适用于所有情况的通用解决方案相比,这实际上毫无价值。
观察者模式不是模式,它是一个类模板。