我应该如何重组此事件处理代码

How should I restructure this event-handling code?

本文关键字:事件处理 代码 重组 何重组 我应该      更新时间:2023-10-16

我最近一直在阅读一些C++书(Sutters,Meyers),这促使我开始更有效地使用智能指针(以及一般的对象破坏)。但是现在我不确定如何解决我所拥有的。具体来说,我现在有一个 IntroScene 类,它继承自 Scene 和 InputListener。

场景并不真正相关,但 InputListener 在构造时订阅了一个 InputManager,并在销毁时再次取消订阅。

class IntroScene : public sfg::Scene, public sfg::InputListener {
/*structors, inherited methods*/
virtual bool OnEvent(sf::Event&) override; //inputlistener
}

但是现在,如果输入管理器将事件发送到场景,并且场景决定自行替换因此,我有一个函数在不再存在的对象上运行。

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() 返回值存储到 OnEvent() 方法的末尾。

真正的问题是输入侦听器

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 并销毁旧 Scene+ Listener 时,列表m_Listeners会在循环过程中被修改。所以事情坏了。我已经考虑过在启动和停止循环时设置一个标志,并将在单独的列表中设置时发生的(取消)订阅存储,并在之后处理。但感觉有点笨拙。

那么,我怎样才能正确地重新设计它以防止这种情况发生呢?提前谢谢。

编辑,解决方案:我最终选择了循环标志和延迟条目列表(下面有 inetknight 的答案)仅适用于订阅,因为以后可以安全地完成。

取消订阅必须立即处理,因此我存储一个(指针可变布尔值)对(可变,因为集合仅返回const_iterator,因此无需存储原始指针)。发生这种情况时,我将布尔值设置为 false,并在事件循环中检查它(请参阅下面的 dave 评论)。不确定这是最干净的解决方案,但它就像一个魅力。非常感谢大家

附带问题:这重要吗?我用谷歌搜索了一下,但没有找到明确的是或否。我确实知道 100% 在被摧毁的对象被销毁后没有调用任何方法。如果有必要,我可以将 Replace() 返回值存储到 OnEvent() 方法的末尾。

如果您知道 100% 没有在销毁的对象上调用任何方法,并且没有访问其成员变量,那么它是安全的。它是否有意取决于您。

您可以有另一个已请求取消/订阅的对象列表。然后,在您告诉事件列表中的每个人之后,您将处理取消/订阅请求列表,然后再继续下一个事件。

/* 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);
    }
}