C++的世界里什么都要自己做

使用Observer模式实现事件的触发、响应机制

最近使用C++进行工作,希望能有一种比较简便易用的消息响应机制,于是想到了observer模式。

实现过程还是比较简单的,如下:

观察者对观察对象提出监听的请求,观察对象将观察者放入自己的监听列表中。当事件发生的时候,观察对象从监听列表中取得观察者的指针一一通知他们。观察者得到这个通知,进行自己的事件响应。使用接口的形式隔离观察者和观察对象之间的直接关联。

可以参见这篇文章:
http://www.cnblogs.com/lane_cn/articles/73240.html

在程序中我是这样实现的:

为了简化客户的调用,我把事件的发起者和捕获者合并成了同一个类,取名叫做CEventHandler,CEventHandler的定义如下:

class CEventHandler
{
public:
    
//! 析构函数
    virtual ~CEventHandler(void);

    
//! 得到事件通知
    virtual void ReceiveEvent(CEventHandler* pSender, int nEventId, void* pArgs)
    
{
        
return;
    }
;

    
//! 注册一个观察对象
    void AddListener(CEventHandler* pListener);

    
//! 注销一个观察对象
    void RemoveListener(CEventHandler* pListener);

protected:
    CEventHandler(
void);

    
//! 通知所有的观察者
    void Notify(int nEventId, void* pArgs);

private:
    
int m_nListenerCount;               //!< 观察者的数量
}
;

AddListener和RemoveListener分别是注册事件监听和注销事件监听的方法,当事件发生的时候,使用Notify通知外界,子类可以重载ReceiveEvent方法捕获事件并且进行相应。

ReceiveEvent没有定义为纯虚函数,这就意味着,子类也可以不捕捉事件。这样符合事件响应机制的原则。

程序实现片断:

void CEventHandler::AddListener(CEventHandler* pListener)
{
    
if (m_nListenerCount + 1 > m_nListSize)
    
{
        
//先把队列的长度延长一倍
        DoubleMemorySize();
    }

    
//Listener加在队列的最后
    m_pListenerList[m_nListenerCount] = pListener;
    m_nListenerCount 
++;
}


void CEventHandler::Notify(int nEventId, void* pArgs)
{
    
for (int i=0; i<m_nListenerCount; i++)
    
{
        
if (m_pListenerList[i] != 0x00)
        
{
            m_pListenerList[i]
->ReceiveEvent(this, nEventId, pArgs);
        }

    }

}


『实现的过程中使用一个CEventHandler*数组来保存监听者的列表,数组的扩大和缩小是自己处理的,为了避免频繁的内存重新分配,影响系统效率,在下标越界的情况下将数组的长度延长一倍,多占一些内存。这种方式模仿了标准模板类中很多集合类的内存管理策略。』

使用的时候,象下面这样:

//! 事件的发起者
class Cast : public CEventHandler
{
public:
    
static const int EVENT_SOMETHING = 101;    //定义一个事件ID

    
//在这个方法中抛出了一个事件
    void SomethingHappen()
    
{
        
//
        
//调用Nodify方法,触发一个事件,包括事件ID和消息的参数<br>
        Nodify(this, EVENT_SOMETHING, pArgs);
    }

}
;

//! 事件的监听者,在监听者中重载ReceiveEvent方法
class Listener : public CEventHandler
{
public:
    Listener()
    
{
        m_pCast 
= new Cast();
        m_pCast
->AddListener(this); //将自己加到事件触发者的监听队列中
    }
;

private:
    Cast
* m_pCast;

    
//事件响应函数,一般情况下定义为private
    virtual void ReceiveEvent(CEventHandler* pSender, int nEventId, void* pArgs)<br>
    
{
        
//捕获事件,判别事件ID,对其进行处理
        
//
        if (Cast::EVENT_SOMETHING == nEventId)
        
{
            
//处理Cast抛出的一个事件
        }

    }

}
;

C++的实现中出现了内存问题 

这样的程序我在Java中曾经使用过,但是在新的环境下却遇到了问题,例如下面的情况:

Listener* listener = new Listener();
Cast
* cast = new Cast();

cast
->AddListener(listener);//将listener加入cast的监听列表中

//程序执行过程

delete listener;
//释放listener

在释放listener之后,listener的指针却仍然存在于cast的监听列表中,并且指针仍然指向原先的位置,当cast对象抛出一个事件,寻找所有的监听者一一通知的时候就遇到了错误,这个listener已经是一个无效的指针了。产生这个错误的原因有下面三个:

1、释放listener指针之前,没有先将listener从cast的监听者中去除;
cast->RemoveListener(listener);
delete listener;

2、程序员没有按照编程规约的要求,在释放一个指针之后将其置为空,影响了程序的判断;
delete listener;
listener 
= 0x00;

3、这个事件响应的机制是有缺陷的。可以在CEventHandler中加入下面的机制:
当listener加入cast的监听者的时候,cast也将自己加入listener的监听者,当listener被析构的时候,采用特别的方式发出一个LISTENER_DELETED事件,cast的基类捕获这个事件,将listener从自己的监听者中去掉。用这样的方式就可以彻底的解决这个问题。

问题的解决

最后在实际的工程中我是用了1和2的办法,个人觉得这样已经可以满足需要,并且迫使程序员更加严格的按照编程规约进行工作。

在C++的世界中真是什么事情都要自己来做。事实上以前用Java或者C#实现的上述功能的时候就是有问题的,没有及时的回收资源,只是由于程序中事件运用的不复杂,加上这些语言的内存是由虚拟机管理,占用过多以后会由虚拟机自动释放,才使得问题没有暴露。

posted on 2005-02-07 01:46  小陆  阅读(1612)  评论(1)    收藏  举报