没有相互引用和智能指针的观察者模式实现
Observer Pattern implementation without reciprocal references and smart pointers
我正在尝试实现观察者模式,但我不希望观察者通过维护 ObservableSubject
中的引用列表来对我的程序安全负责。
意思是当 Observer
object
生命周期结束时,我不想显式调用 ObservervableSubject::removeObserver(&object)
。
我想到了在 ObservableSubject
中使用指针引用的想法。
我的问题是:上面描述的实现和下面尝试的实现是否可能?
我的程序中发生了什么,如何防止取消引用垃圾?
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
您的程序有未定义的行为。
ObservableSubject o;
{
ConcreteObserver c;
o.addListener(&c); // Problem
}
c
在范围结束时被破坏。您最终将过时的指针存储在 o
.
的侦听器列表中
您可以通过在与 o
相同的范围内定义 c
或使用动态分配的内存来解决此问题。
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;
函数结束前。
更新,回应 OP 的评论
你说:
Maybe i wasn't clear. Solving the lifetime/stale pointer problem was the intention of my solution. I know i have no problems if i have properly managed lifetime, or if i add detachObserver
option on Observer destruction. I want to somehow be able to tell from the ObservableSubject
if his list of Observers was corrupted, without the Observer explicitly telling that.
由于取消引用无效指针会导致未定义的行为,因此您必须跟踪观察者的生命周期并确保在必要时更新观察者列表。否则,您就是在追求未定义的行为。
// is this a reference to the copied ptr?
是的,是的。它调用未定义的行为,因为 obs
指针变量在函数末尾超出范围,导致悬空引用。
整个想法对你没有任何好处。即使您使 ref-to-pointer 方法正常工作,您也依赖于一件事:一旦对象死亡,那个确切的 指针变量将设置为 nullptr
。本质上,这与确保 listeners
.
中没有悬挂指针是同一个问题。
对于堆对象:如何确保没有人通过不同的指针删除该对象?或者忘记将已注册的指针清空?对于您的示例中的堆栈对象,情况更糟。该对象超出范围并自动消亡。除非你引入一个你必须手动管理的额外指针变量,否则没有机会使任何东西为空。
您可以考虑两种通用的替代方法:
- 建立双向关系。那么谁先死(observable或者observer)就可以在析构函数中通知对方自己的死
- 如果您不喜欢双向性,一个中央 all-knowing 协调器可以将观察者服务器和可观察对象解耦。当然,这会引入某种全局状态。
Real-life 实现通常朝着利用 C++ 析构函数进行注销的总体方向发展。例如,看看 Qt 的 signal/slot 机制。
注意,我不推荐下面的方法,但我认为它能满足你的要求。您有一个重复的观察者列表。一个由 Observers 控制,另一个使用弱指针,由 Observable 对象处理。
- 将 Observer 构造函数设为私有并使用
ObserverFactory
(这是他们的朋友)获得 std::shared_ptr<Observer>
。工厂有一个从原始指针到引用包装器到关联共享指针的映射。
- 听众列表变为
std::vector<std::weak_ptr<Observer>>
。在列表遍历中,您尝试锁定 weak_ptr;如果成功,处理消息;如果失败,即得到 nullptr
,从列表中删除弱指针。
- 当监听器不想再监听时,它告诉工厂对其共享指针执行
reset
并从映射中移除。这一步比较难看,因为它只是花哨的delete this
,通常是代码味道。
我相信你也可以用 std::shared_from_this
来做到这一点。
计划是将维护从 ObservableSubject 移回 Observers。
我正在尝试实现观察者模式,但我不希望观察者通过维护 ObservableSubject
中的引用列表来对我的程序安全负责。
意思是当 Observer
object
生命周期结束时,我不想显式调用 ObservervableSubject::removeObserver(&object)
。
我想到了在 ObservableSubject
中使用指针引用的想法。
我的问题是:上面描述的实现和下面尝试的实现是否可能? 我的程序中发生了什么,如何防止取消引用垃圾?
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
您的程序有未定义的行为。
ObservableSubject o;
{
ConcreteObserver c;
o.addListener(&c); // Problem
}
c
在范围结束时被破坏。您最终将过时的指针存储在 o
.
您可以通过在与 o
相同的范围内定义 c
或使用动态分配的内存来解决此问题。
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;
函数结束前。
更新,回应 OP 的评论
你说:
Maybe i wasn't clear. Solving the lifetime/stale pointer problem was the intention of my solution. I know i have no problems if i have properly managed lifetime, or if i add
detachObserver
option on Observer destruction. I want to somehow be able to tell from theObservableSubject
if his list of Observers was corrupted, without the Observer explicitly telling that.
由于取消引用无效指针会导致未定义的行为,因此您必须跟踪观察者的生命周期并确保在必要时更新观察者列表。否则,您就是在追求未定义的行为。
// is this a reference to the copied ptr?
是的,是的。它调用未定义的行为,因为 obs
指针变量在函数末尾超出范围,导致悬空引用。
整个想法对你没有任何好处。即使您使 ref-to-pointer 方法正常工作,您也依赖于一件事:一旦对象死亡,那个确切的 指针变量将设置为 nullptr
。本质上,这与确保 listeners
.
对于堆对象:如何确保没有人通过不同的指针删除该对象?或者忘记将已注册的指针清空?对于您的示例中的堆栈对象,情况更糟。该对象超出范围并自动消亡。除非你引入一个你必须手动管理的额外指针变量,否则没有机会使任何东西为空。
您可以考虑两种通用的替代方法:
- 建立双向关系。那么谁先死(observable或者observer)就可以在析构函数中通知对方自己的死
- 如果您不喜欢双向性,一个中央 all-knowing 协调器可以将观察者服务器和可观察对象解耦。当然,这会引入某种全局状态。
Real-life 实现通常朝着利用 C++ 析构函数进行注销的总体方向发展。例如,看看 Qt 的 signal/slot 机制。
注意,我不推荐下面的方法,但我认为它能满足你的要求。您有一个重复的观察者列表。一个由 Observers 控制,另一个使用弱指针,由 Observable 对象处理。
- 将 Observer 构造函数设为私有并使用
ObserverFactory
(这是他们的朋友)获得std::shared_ptr<Observer>
。工厂有一个从原始指针到引用包装器到关联共享指针的映射。 - 听众列表变为
std::vector<std::weak_ptr<Observer>>
。在列表遍历中,您尝试锁定 weak_ptr;如果成功,处理消息;如果失败,即得到nullptr
,从列表中删除弱指针。 - 当监听器不想再监听时,它告诉工厂对其共享指针执行
reset
并从映射中移除。这一步比较难看,因为它只是花哨的delete this
,通常是代码味道。
我相信你也可以用 std::shared_from_this
来做到这一点。
计划是将维护从 ObservableSubject 移回 Observers。