我正在为多线程俄罗斯方块游戏实现类似于观察者设计模式的机制。有一个Game类,其中包含EventHandler对象的集合。如果一个类想将自己注册为Game对象的侦听器,则它必须继承Game::EventHandler类。在状态更改事件上,将在每个侦听器的EventHandler接口(interface)上调用相应的方法。代码如下所示:

class Game
{
public:
    class EventHandler
    {
    public:
        EventHandler();

        virtual ~EventHandler();

        virtual void onGameStateChanged(Game * inGame) = 0;

        virtual void onLinesCleared(Game * inGame, int inLineCount) = 0;

    private:
        EventHandler(const EventHandler&);
        EventHandler& operator=(const EventHandler&);
    };

    static void RegisterEventHandler(ThreadSafe<Game> inGame, EventHandler * inEventHandler);

    static void UnregisterEventHandler(ThreadSafe<Game> inGame, EventHandler * inEventHandler);

    typedef std::set<EventHandler*> EventHandlers;
    EventHandlers mEventHandlers;

private:
    typedef std::set<Game*> Instances;
    static Instances sInstances;
};


void Game::RegisterEventHandler(ThreadSafe<Game> inGame, EventHandler * inEventHandler)
{
    ScopedReaderAndWriter<Game> rwgame(inGame);
    Game * game(rwgame.get());
    if (sInstances.find(game) == sInstances.end())
    {
        LogWarning("Game::RegisterEventHandler: This game object does not exist!");
        return;
    }

    game->mEventHandlers.insert(inEventHandler);
}


void Game::UnregisterEventHandler(ThreadSafe<Game> inGame, EventHandler * inEventHandler)
{
    ScopedReaderAndWriter<Game> rwgame(inGame);
    Game * game(rwgame.get());
    if (sInstances.find(game) == sInstances.end())
    {
        LogWarning("Game::UnregisterEventHandler: The game object no longer exists!");
        return;
    }

    game->mEventHandlers.erase(inEventHandler);
}

这种模式经常遇到两个问题:
  • 侦听器对象希望在一个已删除的对象上注销自身,从而导致崩溃。
  • 向不再存在的侦听器触发事件​​。这种情况最常发生在多线程代码中。这是一个典型的场景:
  • 游戏状态在辅助线程中更改。我们希望通知发生在主线程中。
  • 该事件包装在boost::function中,并作为PostMessage发送到主线程。
  • 不久之后,该功能对象已由主线程处理,而Game对象已被删除。结果是崩溃。

  • 我当前的解决方法是您可以在上面的代码示例中看到的一种。我将UnregisterEventHandler设为一个静态方法,该方法根据实例列表进行检查。这确实有帮助,但是我发现它有些棘手。

    有谁知道一套有关如何干净安全地实现通知者/听众系统的指导方针?关于如何避免上述陷阱的任何建议?

    PS:如果您需要更多信息来回答此问题,可以在此处在线找到相关代码:Game.hGame.cppSimpleGame.hSimpleGame.cppMainWindow.cpp

    最佳答案

  • 经验法则是,对象的delete和new应该彼此靠近。例如。在构造函数和析构函数中,或者在使用对象的调用之前和之后。因此,当后一个对象没有创建前一个对象时,删除另一个对象中的对象是一种不好的做法。
  • 我不明白您如何打包事件。看来您必须在处理事件之前检查游戏是否仍然存在。或者,您可以在事件和其他地方使用shared_ptr来确保最后删除游戏。
  • 10-07 19:16
    查看更多