自己动手编写c++事件模型

在Java、C#等语言或者说其类库中,都实现了事件模型。而c++语言本身并没有定义事件机制,并且在目前众多优秀的c++类库,包括STL、Boost等都没有实现类似的事件机制。当我们被MFC的消息搞得头昏眼花之时,是否有冲动自己去实现一个简单的事件模型呢。我想,有着相同想法的人肯定很多,而真正动手来写可能会碰到各种各样的困难。下面就让我们一步步来编写一个简单的事件模型。

了解事件模型的机制

在开始之前,我们有必要简单的了解一下事件模型的机制。事实上,事件模型的机制不止一种,

Java和c#的事件机制就不太一样,不过事件模型的基础都是一样,那就是一般都使用Observer设计模式。关于Observer设计模式,希望详细了解的朋友可以参考《》书,在这里我们就不详细的介绍,只是参照c#的事件机制来实现。

在c#中,我们可以以event关键字声明一个事件,然后我们可以将一个函数委托挂接在这个事件上,当事件被调用时,则所有挂接的函数也会被调用。这里有一个比较难以理解的词大概是委托,其实也不难理解。委托其实就是一种函数包装类,这种类可以把任何函数的指针保存起来,等到需要调用的时候再调用,并且这种类的实例必须能够挂接到事件之中。

看了上面这段话,是不是觉得其实事件模型也并不复杂。还想对事件模型了解得更多?我这里就不继续了,感兴趣的朋友可以查找一下相关的资料。下面我们就开始在c++中编写一个简单的事件模型。

设计一个简单的c++事件模型

因为一个事件模型其实就是一个典型的Observer设计模式,因此最重要的就是

Subject(目标类)和Observer(观察者类)的设计。

首先,我们需要一个事件类,它其实是一个抽象的Subject。它是一个基类,所有的其他事件都从它继承,并且用户只需要从这个基类继承就可以自定义事件。我们不仿将这个类定义为CEvent。

CEvent的声明如下:

class CEvent
{
public:

typedef list<CEventHandler> data_type;

CEvent();

virtual ~CEvent();

void operator()()
{
    data_type::iterator it;

    for (it = m_observer.begin(); it != m_observer.end(); ++it)
    {
        (*it)(*this);
    }
}

CEvent& operator+= (const CEventHandler& handler)
{
    Register(handler);
    return *this;
}

CEvent& operator-= (const CEventHandler& handler)
{
    UnRegister(handler);
    return *this;
}

void Register(const CEventHandler& handler)
{
    m_observer.push_back(handler);
}

void UnRegister(const CEventHandler& handler)
{
    m_observer.remove(handler);
}

protected:
    data_type m_observer;
};

其次,这个事件可以挂接任意的函数,也就是它可以保存所有的挂接函数。在c++中,我们大致有三种类型的函数,全局或者静态函数、成员函数、仿函数。要在CEvent中实现一个或者多个方法来执行挂接的任务不太现实。我们参考c#的实现方法,可以先实现一个委托类,将各种函数保存起来。然后再使用CEvent对这个委托类进行挂接。我们不仿称这个类为CEventHandler。

然后,我们的这个委托类需要能够保存所有的函数。在c++里,我们能够很容易的得到函数的指针,只需要在函数名前加&,但是各种函数,特别是成员函数它是没有类型的,这就要有一种机制,将所有的函数转换为同一种类型。因为函数是没有类型,也相当于每一个函数都是一种类型,解决这个问题的方法其实就是使用模板类。因此,我们先定义一个虚基类CFunImpl。对三种不同的函数,我们分别从CfunImpl继承三种不同的子类,CstaticFun、CmemFun,CFunctor。

CstaticFun表示全局的或者静态的函数,不过这必须是一个模板类,对于每一个静态或者全局函数,都是模板类的一个特化。类声明的代码如下:

template<typename Fun>
class CStaticFun :

public

CFunImpl {
public:
CStaticFun(const Fun& fun) :
m_fun(fun)
{};

virtual ~CStaticFun()
{
}

protected:
    Fun m_fun;
};

对于CmemFun类,我们如法炮制,不过我们这时需要保存两个成员变量,一个是成员函数的指针,另一个则是该成员函数所属的对象的指针。类声明的代码如下:

template <typename PointerToObj, typename PointerToMemFun>
class CMemFun : public CFunImpl
{
public:

CMemFun(const PointerToObj& pObj, PointerToMemFun pMemFn)
:m_pObj(pObj),
m_pMemFun(pMemFn)
{
}

virtual ~CMemFun()
{
}

protected:

PointerToObj m_pObj;

PointerToMemFun m_pMemFun;
};

对于仿函数,我们这里先不讲,因为仿函数使用得很少,并且这还涉及到仿函数的概念,有兴趣的朋友可以在阅读完本文之后自己来实现。

好,到现在为止,我们可以将所有的函数使用CfunImpl来表示了。因此,我们可以在委托类CEventHandler中保存CfunImpl*来达到我们的目的。CEventHandler的声明如下:

class CEventHandler
{
public:

virtual ~CEventHandler()
{
Clear();
}

template<class Fun>
CEventHandler(const Fun& fun)
:m_pImpl(
new CStaticFun<Fun>(fun))
{}

CEventHandler(const CEventHandler& fun)
:m_pImpl(NULL)
{
*this = fun;
}

void Clear()
{
    if (m_pImpl)
    {
        delete m_pImpl;
        m_pImpl = NULL;
    }
}

CEventHandler& operator= (const CEventHandler& fun)
{
    Clear();

    if (fun.m_pImpl)
    {
        m_pImpl = fun.m_pImpl

        -&gt;

        Clone();
    }

    return *this;
}

//成员函数
template <typename PointerToObj, typename PointerToMemFun>
CEventHandler(const PointerToObj& pObj, const PointerToMemFun& pMemFun)
:m_pImpl(new CMemFun<PointerToObj, PointerToMemFun>(pObj, pMemFun))
{
}

void operator()(CEvent& e)
{
    if (m_pImpl)
    {
        (*m_pImpl)(e);
    }
}

bool operator== (const CEventHandler& handler)
{
    if (m_pImpl == NULL || handler.m_pImpl == NULL)
    {
        return true;
    }

    if (typeid(m_pImpl) == typeid(handler.m_pImpl))
    {
        return (*m_pImpl) == (*(handler.m_pImpl));
    }

    return false;
}

protected:

CFunImpl *m_pImpl;

};

不过要实现事件机制,我们还需要CfunImpl*的子类必须实现几个方法。第一个方法就是调用函数的方法,我们这里要求重载

void operator()(CEvent&amp; e);

来达到目的。

第二个方法就是operator==,为什么需要这个方法呢。因为对于CEvent来说,调用UnRegister()时,我们必须找到我们使用Register()添加到list中的函数,这时我们需要判断两个CEventHandler对象是否相等,这是件麻烦事情。CEventHandler保存的是CFunImpl的指针,好吧,这个我们就要求CfunImpl的子类必须实现重载operator==

第三个方法,从CfunImpl继承必须实现Clone()方法。Clone()方法可以让CEventHandler实现拷贝。具体的实现方法我们可以查看源码。

使用事件模型

一个简单的事件模型基本完成了。现在我们可以编写一个例子来演示一下如何来使用这个事件模型。其实使用起来很简单。

我们可以声明一个CEvent或者CEvent子类的实例。让需要监听的函数挂接到这个CEvent中,我们实现了Register()函数和+=操作符,都可以使用。挂接的语句就像这样:

CEvent evt;

CobserverTest obj;

evt +=CEventHandler(&obj

, &amp;(

CObserverTest::OnStartEvent

));

当事件的operator()方法调用时,就会自动调用我们的监听函数。具体的例子可以查看提供的源代码。

模型的缺陷和改进

到现在为止,我们终于实现了一个简单的事件模型。它可以有效的工作,并且与平台无关。当然,要求你的编译器尽量的支持c++标准。如果你的编译器对c++98标准支持不够,也许它不能够工作正常。笔者在VC6SP6VC2005中测试均通过。

不过这个模型还有可以改进的余地。

  • 目前只是支持同步操作,即当你的事件进行调用时是在同一线程里顺序进行。要改进到支持异步调用就必须在每次调用时在一个新的线程中进行调用。因此我们需要一个线程机制来完成这个工作。如果在一个特定的平台中使用,如VC中,我们可以在响应函数中创建一个新的线程。

  • 目前的所有的函数参数必须为(CEvent& e),不能写成(CchildEvent& e)。要对这个限制进行改进,就需要编译器支持虚模板成员函数的机制。不过在VC6VC2005中,均不支持这种特性。因此要么寻求它法,要么就等到编译器的支持吧。

  • 线程安全,这个简单的事件模型使用到了STL中的list组件,但是并非所有的list实现都是线程安全的。建议大家采用stlport,因为它的实现是线程安全的,如果没有采用线程安全的实现,那么在调用RegisterUnRegister时最好能够实现互斥。

限于笔者知识水平和文笔有限,不当之处肯定很多。若你有好的想法和建议,可以email告诉我。

blog comments powered by Disqus