我应该如何重构这个事件处理代码?

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