Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/apache-flex/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 如何安全地实现观察者模式?_C++_Observer Pattern - Fatal编程技术网

C++ 如何安全地实现观察者模式?

C++ 如何安全地实现观察者模式?,c++,observer-pattern,C++,Observer Pattern,我正在实现一种类似于多线程俄罗斯方块游戏的观察者设计模式的机制。有一个包含EventHandler对象集合的游戏类。如果一个类想将自己注册为游戏对象的侦听器,它必须继承Game::EventHandler类。在状态更改事件中,在每个侦听器的EventHandler接口上调用相应的方法。这是代码的样子: class Game { public: class EventHandler { public: EventHandler(); vir

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

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);
}
virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, int value); 
virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, const string& str);
类游戏
{
公众:
类事件处理程序
{
公众:
EventHandler();
虚拟~EventHandler();
虚拟无效状态已更改(游戏*inGame)=0;
虚拟虚空在线清理(Game*inGame,int inLineCount)=0;
私人:
EventHandler(consteventhandler&);
EventHandler&运算符=(const EventHandler&);
};
静态无效注册表venthandler(线程安全inGame、EventHandler*inEventHandler);
静态void UnregisterEventHandler(线程安全inGame、EventHandler*inEventHandler);
typedef std::set EventHandlers;
事件处理程序Meventhandler;
私人:
typedef std::set实例;
静态实例;
};
void Game::RegisterEventHandler(线程安全inGame,EventHandler*inEventHandler)
{
ScopeDreanderWriter rwgame(inGame);
Game*Game(rwgame.get());
if(sInstances.find(game)=sInstances.end())
{
LogWarning(“Game::RegisterEventHandler:此游戏对象不存在!”);
返回;
}
游戏->Meventhandler.insert(Inventhandler);
}
无效游戏::取消注册EventHandler(线程安全inGame、EventHandler*inEventHandler)
{
ScopeDreanderWriter rwgame(inGame);
Game*Game(rwgame.get());
if(sInstances.find(game)=sInstances.end())
{
LogWarning(“Game::UnregisterEventHandler:游戏对象不再存在!”);
返回;
}
游戏->Meventhandler.擦除(Inventhandler);
}
在这种模式下,我经常遇到两个问题:

  • 侦听器对象希望在已删除的对象上注销自身,从而导致崩溃
  • 事件被激发到不再存在的侦听器。这在多线程代码中最常见。下面是一个典型的场景:
    • 工作线程中的游戏状态更改。我们希望通知发生在主线程中
    • 该事件包装在boost::函数中,并作为PostMessage发送到主线程
    • 不久之后,当游戏对象已被删除时,该函数对象由主线程处理。结果是一场崩溃
  • 我当前的解决方法就是您可以在上面的代码示例中看到的方法。我使
    UnregisterEventHandler
    成为一个静态方法,用于检查实例列表。这确实有帮助,但我觉得这是一个有点粗俗的解决方案

    有人知道关于如何干净安全地实现通知程序/侦听器系统的一组指导原则吗?有没有关于如何避免上述陷阱的建议

    注:如果你需要更多信息来回答这个问题,你可以在这里在线找到相关代码:

  • 经验法则是对象的delete和new应该彼此靠近。例如,在构造函数和析构函数中,或者在使用对象的调用前后。因此,当另一个对象没有创建前一个对象时,删除另一个对象中的对象是一种糟糕的做法

  • 我不明白你是怎么安排这些活动的。在处理事件之前,您似乎必须检查游戏是否仍处于活动状态。或者,您可以在活动和其他地方使用
    共享\u ptr
    ,以确保游戏最后被删除


  • <>我写了很多C++代码,需要为我正在开发的游戏组件创建一个观察者。我需要一些东西来分发“开始帧”、“用户输入”等,作为游戏中的事件分发给感兴趣的各方。我也有同样的问题要考虑……一个事件的爆发会导致另一个观察者的毁灭,这也可能随后发生。我需要处理这件事。我不需要处理线程安全,但我通常追求的设计要求是构建足够简单(API方面)的东西,我可以在正确的位置放入一些互斥体,其余的应该自行处理

    <>我也希望它是直的C++,不依赖于平台或特定的技术(比如Boost、Qt等),因为我经常在不同的项目中构建和重用组件(以及它们背后的想法)。 以下是我提出的解决方案的大致草图:

  • 观察者是一个具有键(枚举值,而不是字符串)的单例,供主体注册感兴趣的对象。因为它是一个独生子,所以它总是存在的
  • 每个主题都派生自一个公共基类。基类有一个抽象的虚函数Notify(…),必须在派生类中实现,还有一个析构函数,当它被删除时,它会将它从观察者(它总是可以到达)中删除
  • 在观察者本身内部,如果在进行通知(…)时调用Detach(…),则任何分离的主题都会出现在列表中
  • 当对观察者调用Notify(…)时,它将创建主题列表的临时副本。当它迭代它时,它会将它与最近分离的对象进行比较。如果目标不在其上,则对目标调用Notify(…)。否则,将跳过它
  • Observer中的Notify(…)还跟踪处理级联调用的深度(A通知B、C、D,D.Notify(…)触发对E的Notify(…)调用,等等)
  • 这就是界面最终的样子:

    /* 
     The Notifier is a singleton implementation of the Subject/Observer design
     pattern.  Any class/instance which wishes to participate as an observer
     of an event can derive from the Notified base class and register itself
     with the Notiifer for enumerated events.
    
     Notifier derived classes MUST implement the notify function, which has 
     a prototype of:
    
     void Notify(const NOTIFIED_EVENT_TYPE_T& event)
    
     This is a data object passed from the Notifier class.  The structure 
     passed has a void* in it.  There is no illusion of type safety here 
     and it is the responsibility of the user to ensure it is cast properly.
     In most cases, it will be "NULL".
    
     Classes derived from Notified do not need to deregister (though it may 
     be a good idea to do so) as the base class destrctor will attempt to
     remove itself from the Notifier system automatically.
    
     The event type is an enumeration and not a string as it is in many 
     "generic" notification systems.  In practical use, this is for a closed
     application where the messages will be known at compile time.  This allows
     us to increase the speed of the delivery by NOT having a 
     dictionary keyed lookup mechanism.  Some loss of generality is implied 
     by this.
    
     This class/system is NOT thread safe, but could be made so with some
     mutex wrappers.  It is safe to call Attach/Detach as a consequence 
     of calling Notify(...).  
    
     */
    
    
    class Notified;
    
    class Notifier : public SingletonDynamic<Notifier>
    {
    public:
       typedef enum
       {
          NE_MIN = 0,
          NE_DEBUG_BUTTON_PRESSED = NE_MIN,
          NE_DEBUG_LINE_DRAW_ADD_LINE_PIXELS,
          NE_DEBUG_TOGGLE_VISIBILITY,
          NE_DEBUG_MESSAGE,
          NE_RESET_DRAW_CYCLE,
          NE_VIEWPORT_CHANGED,
          NE_MAX,
       } NOTIFIED_EVENT_TYPE_T;
    
    private:
       typedef vector<NOTIFIED_EVENT_TYPE_T> NOTIFIED_EVENT_TYPE_VECTOR_T;
    
       typedef map<Notified*,NOTIFIED_EVENT_TYPE_VECTOR_T> NOTIFIED_MAP_T;
       typedef map<Notified*,NOTIFIED_EVENT_TYPE_VECTOR_T>::iterator NOTIFIED_MAP_ITER_T;
    
       typedef vector<Notified*> NOTIFIED_VECTOR_T;
       typedef vector<NOTIFIED_VECTOR_T> NOTIFIED_VECTOR_VECTOR_T;
    
       NOTIFIED_MAP_T _notifiedMap;
       NOTIFIED_VECTOR_VECTOR_T _notifiedVector;
       NOTIFIED_MAP_ITER_T _mapIter;
    
       // This vector keeps a temporary list of observers that have completely
       // detached since the current "Notify(...)" operation began.  This is
       // to handle the problem where a Notified instance has called Detach(...)
       // because of a Notify(...) call.  The removed instance could be a dead
       // pointer, so don't try to talk to it.
       vector<Notified*> _detached;
       int32 _notifyDepth;
    
       void RemoveEvent(NOTIFIED_EVENT_TYPE_VECTOR_T& orgEventTypes, NOTIFIED_EVENT_TYPE_T eventType);
       void RemoveNotified(NOTIFIED_VECTOR_T& orgNotified, Notified* observer);
    
    public:
    
       virtual void Reset();
       virtual bool Init() { Reset(); return true; }
       virtual void Shutdown() { Reset(); }
    
       void Attach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType);
       // Detach for a specific event
       void Detach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType);
       // Detach for ALL events
       void Detach(Notified* observer);
    
       /* The design of this interface is very specific.  I could 
        * create a class to hold all the event data and then the
        * method would just have take that object.  But then I would
        * have to search for every place in the code that created an
        * object to be used and make sure it updated the passed in
        * object when a member is added to it.  This way, a break
        * occurs at compile time that must be addressed.
        */
       void Notify(NOTIFIED_EVENT_TYPE_T, const void* eventData = NULL);
    
       /* Used for CPPUnit.  Could create a Mock...maybe...but this seems
        * like it will get the job done with minimal fuss.  For now.
        */
       // Return all events that this object is registered for.
       vector<NOTIFIED_EVENT_TYPE_T> GetEvents(Notified* observer);
       // Return all objects registered for this event.
       vector<Notified*> GetNotified(NOTIFIED_EVENT_TYPE_T event);
    };
    
    /* This is the base class for anything that can receive notifications.
     */
    class Notified
    {
    public:
       virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, const void* eventData) = 0;
       virtual ~Notified();
    
    };
    
    typedef Notifier::NOTIFIED_EVENT_TYPE_T NOTIFIED_EVENT_TYPE_T;
    
    相应的Notify(…)方法已添加到通知程序本身。所有这些都使用单个函数获取“目标列表”,然后对目标调用相应的函数。这样做效果很好,使接受者不必做难看的演员

    这似乎很有效。解决方案发布在