我应该如何重构这个事件处理代码?
How should I restructure this event-handling code?
我最近一直在阅读一些 C++ 书籍(Sutters、Meyers),这促使我开始更有效地使用智能指针(以及一般的对象破坏)。但是现在我不确定如何修复我所拥有的。
具体来说,我现在有一个继承自 Scene 和 InputListener 的 IntroScene class。
场景并不真正相关,但 InputListener 在构建时订阅了 InputManager,
并在销毁时再次取消订阅。
class IntroScene : public sfg::Scene, public sfg::InputListener {
/*structors, inherited methods*/
virtual bool OnEvent(sf::Event&) override; //inputlistener
}
但是现在,如果 inputmanager 将事件发送到场景,并且场景决定替换自己
因此,我在一个不再存在的对象上有函数 运行。
bool IntroScene::OnEvent(sf::Event& a_Event) {
if (a_Event.type == sf::Event::MouseButtonPressed) {
sfg::Game::Get()->SceneMgr()->Replace(ScenePtr(new IntroScene()));
} //here the returned smartpointer kills the scene/listener
}
附带问题:这重要吗?我用谷歌搜索但没有找到明确的是或否。我确实知道 100%
被销毁的对象在被销毁后不会调用任何方法。
如果必须的话,我可以存储 Replace() return 值直到 OnEvent() 方法结束。
真正的问题是 InputListener
InputListener::InputListener() {
Game::Get()->InputMgr()->Subscribe(this);
}
InputListener::~InputListener() {
if (m_Manager) m_Manager->Unsubscribe(this);
}
因为它是在 OnEvent() 期间调用的,在 HandleEvents() 期间由 InputManager 调用
void InputManager::HandleEvents(EventQueue& a_Events) const {
while (!a_Events.empty()) {
sf::Event& e = a_Events.front();
for (auto& listener : m_Listeners) {
if (listener->OnEvent(e)) //swallow event
break;
}
a_Events.pop();
}
void InputManager::Subscribe(InputListener* a_Listener) {
m_Listeners.insert(a_Listener);
a_Listener->m_Manager = this;
}
void InputManager::Unsubscribe(InputListener* a_Listener) {
m_Listeners.erase(a_Listener);
a_Listener->m_Manager = nullptr;
}
所以当新的Scene+Listener被创建,旧的被销毁时,列表m_Listeners在循环中被修改。所以事情坏了。
我考虑过在启动和停止循环时设置一个标志,并将它设置在单独列表中时发生的(取消)订阅存储起来,然后再处理。但是感觉有点hacky。
那么,我怎样才能真正正确地重新设计它以防止出现这些情况呢?提前致谢。
编辑,解决方案:
我最终使用了循环标志和延迟条目列表(下面是 inetknight 的回答)
仅供订阅,因为以后可以安全地完成订阅。
取消订阅必须立即处理,所以我没有存储原始指针,而是存储了一个(指针-可变布尔)对(可变,因为一个集合只有 return 一个 const_iterator)。当发生这种情况时,我将 bool 设置为 false 并在事件循环中检查它(参见下面戴夫的评论)。
不确定它是否是最干净的解决方案,但它就像一个魅力。非常感谢大家
Side-question: Does that matter? I googled it but did not find a definite yes or no. I do know 100% no methods are invoked on the destroyed object after it is destroyed. I can store the Replace() return value until the end of the OnEvent() method if I have to.
如果您 100% 知道在销毁对象并且访问其 none 个成员变量时没有调用任何方法,那么它是 安全的 。是否有意由您决定。
您可以有另一个已请求 un/subscribed 的对象列表。然后在您告诉事件列表中的每个人之后,您将在继续下一个事件之前处理 un/subscription 请求列表。
/* this should be a member of InputManager however you did not provide a class definition */
typedef std::pair<InputListener *, bool> SubscriptionRequest;
bool handleEventsActive = false;
std::vector<SubscriptionRequest> deferredSubscriptionRequests;
void InputManager::HandleEvents(EventQueue& a_Events) const {
// process events
handleEventsActive = true;
while (!a_Events.empty()) {
sf::Event& e = a_Events.front();
for (auto& listener : m_Listeners)
{
//swallow event
if (listener->OnEvent(e)) {
break;
}
}
a_Events.pop();
// process deferred subscription requests occurred during event
while ( not deferredSubscriptionRequests.empty() ) {
SubscriptionRequest request = deferredSubscriptionRequests.back();
deferredSubscriptionRequests.pop_back();
DoSubscriptionRequest(request);
}
}
handleEventsActive = false;
}
void InputManager::DoSubscriptionRequest(SubscriptionRequest &request) {
if ( request.second ) {
m_Listeners.insert(request.first);
request.first->m_Manager = this;
} else {
m_Listeners.erase(request.first);
request.first->m_Manager = nullptr;
}
}
void InputManager::Subscribe(InputListener* a_Listener)
{
SubscriptionRequest request{a_Listener, true};
if ( handleEventsActive ) {
deferredSubscriptionRequests.push_back(request);
} else {
DoSubscriptionRequest(request);
}
}
void InputManager::Unsubscribe(InputListener* a_Listener)
{
SubscriptionRequest request{a_Listener, false};
if ( handleEventsActive ) {
deferredSubscriptionRequests.push_back(request);
} else {
DoSubscriptionRequest(request);
}
}
我最近一直在阅读一些 C++ 书籍(Sutters、Meyers),这促使我开始更有效地使用智能指针(以及一般的对象破坏)。但是现在我不确定如何修复我所拥有的。 具体来说,我现在有一个继承自 Scene 和 InputListener 的 IntroScene class。
场景并不真正相关,但 InputListener 在构建时订阅了 InputManager, 并在销毁时再次取消订阅。
class IntroScene : public sfg::Scene, public sfg::InputListener {
/*structors, inherited methods*/
virtual bool OnEvent(sf::Event&) override; //inputlistener
}
但是现在,如果 inputmanager 将事件发送到场景,并且场景决定替换自己 因此,我在一个不再存在的对象上有函数 运行。
bool IntroScene::OnEvent(sf::Event& a_Event) {
if (a_Event.type == sf::Event::MouseButtonPressed) {
sfg::Game::Get()->SceneMgr()->Replace(ScenePtr(new IntroScene()));
} //here the returned smartpointer kills the scene/listener
}
附带问题:这重要吗?我用谷歌搜索但没有找到明确的是或否。我确实知道 100% 被销毁的对象在被销毁后不会调用任何方法。 如果必须的话,我可以存储 Replace() return 值直到 OnEvent() 方法结束。
真正的问题是 InputListener
InputListener::InputListener() {
Game::Get()->InputMgr()->Subscribe(this);
}
InputListener::~InputListener() {
if (m_Manager) m_Manager->Unsubscribe(this);
}
因为它是在 OnEvent() 期间调用的,在 HandleEvents() 期间由 InputManager 调用
void InputManager::HandleEvents(EventQueue& a_Events) const {
while (!a_Events.empty()) {
sf::Event& e = a_Events.front();
for (auto& listener : m_Listeners) {
if (listener->OnEvent(e)) //swallow event
break;
}
a_Events.pop();
}
void InputManager::Subscribe(InputListener* a_Listener) {
m_Listeners.insert(a_Listener);
a_Listener->m_Manager = this;
}
void InputManager::Unsubscribe(InputListener* a_Listener) {
m_Listeners.erase(a_Listener);
a_Listener->m_Manager = nullptr;
}
所以当新的Scene+Listener被创建,旧的被销毁时,列表m_Listeners在循环中被修改。所以事情坏了。 我考虑过在启动和停止循环时设置一个标志,并将它设置在单独列表中时发生的(取消)订阅存储起来,然后再处理。但是感觉有点hacky。
那么,我怎样才能真正正确地重新设计它以防止出现这些情况呢?提前致谢。
编辑,解决方案: 我最终使用了循环标志和延迟条目列表(下面是 inetknight 的回答) 仅供订阅,因为以后可以安全地完成订阅。
取消订阅必须立即处理,所以我没有存储原始指针,而是存储了一个(指针-可变布尔)对(可变,因为一个集合只有 return 一个 const_iterator)。当发生这种情况时,我将 bool 设置为 false 并在事件循环中检查它(参见下面戴夫的评论)。 不确定它是否是最干净的解决方案,但它就像一个魅力。非常感谢大家
Side-question: Does that matter? I googled it but did not find a definite yes or no. I do know 100% no methods are invoked on the destroyed object after it is destroyed. I can store the Replace() return value until the end of the OnEvent() method if I have to.
如果您 100% 知道在销毁对象并且访问其 none 个成员变量时没有调用任何方法,那么它是 安全的 。是否有意由您决定。
您可以有另一个已请求 un/subscribed 的对象列表。然后在您告诉事件列表中的每个人之后,您将在继续下一个事件之前处理 un/subscription 请求列表。
/* this should be a member of InputManager however you did not provide a class definition */
typedef std::pair<InputListener *, bool> SubscriptionRequest;
bool handleEventsActive = false;
std::vector<SubscriptionRequest> deferredSubscriptionRequests;
void InputManager::HandleEvents(EventQueue& a_Events) const {
// process events
handleEventsActive = true;
while (!a_Events.empty()) {
sf::Event& e = a_Events.front();
for (auto& listener : m_Listeners)
{
//swallow event
if (listener->OnEvent(e)) {
break;
}
}
a_Events.pop();
// process deferred subscription requests occurred during event
while ( not deferredSubscriptionRequests.empty() ) {
SubscriptionRequest request = deferredSubscriptionRequests.back();
deferredSubscriptionRequests.pop_back();
DoSubscriptionRequest(request);
}
}
handleEventsActive = false;
}
void InputManager::DoSubscriptionRequest(SubscriptionRequest &request) {
if ( request.second ) {
m_Listeners.insert(request.first);
request.first->m_Manager = this;
} else {
m_Listeners.erase(request.first);
request.first->m_Manager = nullptr;
}
}
void InputManager::Subscribe(InputListener* a_Listener)
{
SubscriptionRequest request{a_Listener, true};
if ( handleEventsActive ) {
deferredSubscriptionRequests.push_back(request);
} else {
DoSubscriptionRequest(request);
}
}
void InputManager::Unsubscribe(InputListener* a_Listener)
{
SubscriptionRequest request{a_Listener, false};
if ( handleEventsActive ) {
deferredSubscriptionRequests.push_back(request);
} else {
DoSubscriptionRequest(request);
}
}