我试图实施观察者模式,但我不希望观察者通过维护ObservableSubject
中的参考列表来对我的程序安全负责。
这意味着当Observer
object
生命周期结束时,我不想明确调用ObservervableSubject::removeObserver(&object)
。
我想出了在ObservableSubject
中使用use指针引用的想法。
我的问题是:上面描述的实现和下面的尝试是否可行? 在我的程序中发生了什么,我如何防止解除引用垃圾?
Apriori借口:这是尝试理解C ++,而不是应该实际使用或优先于其他实现的东西。
我的解决方案尝试:
// Example program
#include <iostream>
#include <string>
#include <vector>
class ObserverInterface {
public:
virtual ~ObserverInterface() {};
virtual void handleMessage() = 0;
};
class ObservableSubject
{
std::vector<std::reference_wrapper<ObserverInterface*>> listeners;
public:
void addObserver(ObserverInterface* obs)
{
if (&obs)
{
// is this a reference to the copied ptr?
// still, why doesnt my guard in notify protect me
this->listeners.push_back(obs);
}
}
void removeObserver(ObserverInterface* obs)
{
// todo
}
void notify()
{
for (ObserverInterface* listener : this->listeners)
{
if (listener)
{
listener->handleMessage();
}
}
}
};
class ConcreteObserver : public ObserverInterface {
void handleMessage()
{
std::cout << "ConcreteObserver: I'm doing work..." << std::endl;
}
};
int main()
{
ObservableSubject o;
{
ConcreteObserver c;
o.addListener(&c);
}
o.notify();
std::cin.get();
}
ObservableSubject::notify()
中的行:Listener->handleMessage()
会引发以下异常:
Exception thrown: read access violation.
listener->**** was 0xD8BF48B. occurred
答案 0 :(得分:5)
您的程序有不确定的行为。
ObservableSubject o;
{
ConcreteObserver c;
o.addListener(&c); // Problem
}
当范围结束时, c
被破坏。您最终将一个过时的指针存储在o
的侦听器列表中。
您可以通过在与c
相同的范围内定义o
或使用动态分配的内存来解决此问题。
ObservableSubject o;
ConcreteObserver c;
o.addListener(&c);
或
ObservableSubject o;
{
ConcreteObserver* c = new ConcreteObserver;
o.addListener(c);
}
使用动态分配的内存时,附加范围无用。你也可以不使用它。
ObservableSubject o;
ConcreteObserver* c = new ConcreteObserver;
o.addListener(c);
如果您选择使用第二种方法,请确保取消分配内存。你需要添加
delete c;
在函数结束之前。
你说:
也许我不清楚。解决生命周期/陈旧指针问题是我的解决方案的目的。我知道如果我有正确的托管生命周期,或者如果我在Observer destroy上添加
detachObserver
选项,我就没有问题。我希望以某种方式能够告诉ObservableSubject
他的观察者列表是否已损坏,而Observer没有明确说明。
由于取消引用无效指针是未定义行为的原因,因此必须跟踪观察者的生命周期并确保在必要时更新观察者列表。没有它,你就是在追求未定义的行为。
答案 1 :(得分:2)
//这是对复制的ptr的引用吗?
是的,确实如此。它调用未定义的行为,因为obs
指针变量在函数末尾超出范围,导致悬空引用。
整个想法并没有给你带来任何好处。即使你使ref-to-pointer方法正常工作,你依赖于一件事:一旦对象死亡,那个精确的指针变量被设置为nullptr
。基本上这与确保listeners
中没有悬空指针的问题相同。
对于堆对象:如何确保没有人通过不同的指针删除对象?或者忘记将已注册的指针置空?对于像您的示例中的堆栈对象来说,情况更糟。该对象超出范围并自动死亡。除非你引入了一个你必须手动管理的附加指针变量,否则没有机会取消任何内容。
您可以考虑两种常用的替代方法:
实际实现通常是利用C ++析构函数进行注销的总体方向。例如,看看Qt的信号/插槽机制。
答案 2 :(得分:2)
注意,我不推荐以下方法,但我认为它符合您的要求。您有一个重复的观察者列表。一个是在观察者的控制之下,另一个是使用弱指针,由Observable对象处理。
ObserverFactory
(这是他们的朋友)获取std::shared_ptr<Observer>
。工厂有一个从原始指针到引用包装器到相关共享指针的映射。std::vector<std::weak_ptr<Observer>>
。在列表遍历上,您尝试锁定weak_ptr;如果成功,处理消息;如果它失败了,就是你得到nullptr
,从列表中删除弱指针。reset
并从地图中删除。这个步骤相当丑陋,因为它只是一个花哨的delete this
,通常是代码味道。我相信您也可以使用std::shared_from_this
。
计划是将维护从ObservableSubject移回观察者。