事件支持已经是
Delphi,Java,C#这样的后起语言的语法之一,但是在C++中并没有显示的支持。不同的编译器采用各自的方法来提供对事件的支持,例如:Borland C++ Builder通过扩展语法来提供事件支持,以实现VCL;MFC的事件是在设计时由向导生成的基于表格驱动的静态事件,不提供运行时支持。这两种实现都只能在特定场景下工作,而且难于移植。因此,我们需要一个完全基于C++语法的事件机制,不能使用编译器对C++语法的扩展关键字,并且,该机制应该是动态的,即能够在运行时改变事件属性,这个属性通常是指与事件相关联的事件处理过程的方法或函数的地址,能够动态个性属性的事件具有更大的灵活性和易于使用。
要实现这一目的,可以参考一下
Delphi,Java,C#,包括BCB的VCL,在这些语言中(或开发工具)中事件的实现或表示机制,还可以参考一下COM的连接点技术。很容易就可以发现,它们有一个共同点:所有对象都源自一个公共基类,尤其是BCB,如果对象不是继承自TObject的话,就不能使用事件。参考COM的连接点实现,也可以发现一些有意思的东西,特别是它调用连接点事件的手段。同时,MFC建立事件表格的方法,也值得借鉴。
使用公共基类,派生对象可以向上映射,这样就可以对事件的表现形式进行统一定义。使用公共基类还有另外一个重要意义:当我们在
C++中获取对象的方法地址时,实际上取得是对象方法在虚表中偏移,而源自相同基类的对象的方法的偏移是各不相同的,即它们的虚表具有统一的布局,而源自不同基类的对象的方法则可能相同。
在进行下一步讨论之前,我们先看看在
C++中如何通过指针来调用对象的方法。
在
C++中,可以通过以下形式来获得一个对象的方法地址:
&对象::方法
这样得到的地址其实是方法在对象的虚表内的16位偏移,因此,还需要一个对象的实例指针才能正确的调用方法。可以使用以下语句来调用实例中的方法:
(对象的实例指针->*方法地址)(参数)
通常,我们需要将方法地址映射为一个通用的表现形式:
static_cast<通用的方法表现形式>(&对象::方法)
举例来说,对于两个类A,B的方法
A::func1,B::func2,当它们源自相同基类时,则在虚表中具有不同偏移,一个可能为1,另一个可能为5,但绝不会相同,假设取得的地址为:addr1,addr2。
那么就有:
addr1 = static_cast<表现定义>(&A::func1);
addr2 = static_cast<表现定义>(&B::func2);
无论何时,如果按以下形式调用两个方法,则是正确的:
(对象X指针->*addr1)();// 这会调用偏移1处的方法
(对象Y指针->*addr2)();// 这会调用偏移5处的方法
但是,当它们源自不同基类时,则无法进行统一形式的static_cast映射,并且,方法有可能具有相同的偏移,这样两个调用都会尝试同一个方法,这会导致错误。
通过公共基类,可以将事件的处理方法属性关联到任意符合定义的方法上,这样就可以在运行时修改事件属性,实现动态事件机制。当然,在设置事件的关联处理方法-通常叫事件句柄(handler)-的同时,我们还需要保存一个具有该方法的对象的实例指针。这样当事件发生时,我们就能够在正确的对象实例上调用正确的方法,完成正确的事件处理。
下面是基于上面方法的的实现代码,使用了
BCB的命名习惯和doxgen的注释风格:
/**
* 基类
*/
class
TObject {};
/**
* 事件参数类
*/
template
< class T >
class
TEventArg : public TObject
{
T value;
public
:
TEventArg():value() { }
TEventArg(T x) { value = x; }
TEventArg(const TEventArg& ref) { value = ref; }
TEventArg<T>& operator = (T x)
{
value = x;
return *this;
}
TEventArg<T>& operator = (TEventArg<T> x)
{
value = x;
return *this;
}
operator T()
{
return value;
}
};
/**
* 事件类
*/
template
< class T>
class
TEvent : public TObject
{
public
:
/**
* 事件句柄类型定义
*/
typedef void (TObject::*TEventHandler)(TEventArg<T>[]);
private
:
/**
* 保存事件处理方法的句柄数组
* 通常只要保存一个事件处理方法就可以
* 这里是为了演示一个事件上激发多个处理句柄的情况,使用了数组来
* 保存所有句柄
*/
vector<pair<TObject*, TEventHandler> > _HandlerArray;
public
:
/**
* 清除所有事件处理方法的句柄
*/
void clear()
{
_HandlerArray.clear();
}
/**
* 移除事件处理方法句柄
*/
void remove(pair<TObject*, TEventHandler> handler)
{
// 通常事件只有一个或数个,所以为了简单起见和说明用途,使用了for循环
for ( vector<pair<TObject*, TEventHandler> >::iterator it = _HandlerArray.begin();
it != _handlerArray.end(); it++ )
{
if ( (*it).first == handler.first &&
*(int *).second == *(int *)handler.second )
{
it = _HandlerArray.erase(it);
}
}
}
/**
* 句柄的赋值操作,清空已有的句柄,并用新句柄代替
* @see add
*/
TEvent<T>& operator = (pair<TObject*, TEventHandler> handler)
{
clear();
_HandlerArray.push_back(handler);
return *this;
}
/**
* 添加事件处理方法句柄,在实际情况中,比operator +=操作简单
*/
void add(TObject* object, TEventHandler handler)
{
_HandlerArray.push_back(make_pair<TObject*, TEventHandler>(object, handler));
}
/**
* 添加事件处理方法句柄
*/
TEvent<T>& operator += (pair<TObject*, TEventHandler> handler)
{
_HandlerArray.push_back(handler);
return *this;
}
/**
* 删除句柄
*/
TEvent<T>& operator += (pair<TObject*, TEventHandler> handler)
{
remove(handler);
return *this;
}
/**
* 激发事件
*/
void fire(TEventArg<T> args[])
{
/**
* 循环激发所有事件
*/
for(vector<pair<TObject*, TEventHandler> >::iterator it = _HandlerArray.begin();
it != _HandlerArray.end(); it++)
{
if ( (*it).first != NULL && (*it).second != NULL )
{
// 这里是示意如何调用事件句柄,形成激发事件的
// 为了示意,写成三步
TObject* object = (*it).first;
TEventHandler handler = (*it).second;
(object->*handler)(args);
//((*it).first->*((*it).second))(args); // 简短的形式一步就可以了
} // if
}// for
}// method fire
};
/**
* 事件的激发源
*/
template
< class T >
class
TMyEventFirer : public TObject
{
public
:
/// 事件
TEvent<T> MyEvent;
/// 某个将会激发事件的方法
void fireEvent(TEventArg<T> args[])
{
MyEvent.fire(args);
}
};
/**
* 处理事件的对象之一
*/
class
A : public TObject
{
public
:
A() { }
public
:
/**
* 事件处理方法
*/
void func(TEventArg<int> args[])
{
cout<<"A:func:"<<(int)args[0]<<endl;
}
};
/**
* 处理事件的对象之二
*/
class
B : public TObject
{
public
:
B() { }
public
:
/**
* 事件处理方法
*/
void func(TEventArg<int> args[])
{
cout<<"B:func:"<<(int)args[0]<<endl;
}
};
int
_tmain(int argc, _TCHAR* argv[])
{
TMyEventFirer<int> firer; // 事件源
A a; // 事件处理对象
B b; // 事件处理对象
//添加A对象的事件处理
firer.MyEvent += make_pair<TObject*, TEvent<int>::TEventHandler>(&a, static_cast<TEvent<int>::TEventHandler>(&A::func));
//添加B对象的事件处理
firer.MyEvent += make_pair<TObject*, TEvent<int>::TEventHandler>(&b, static_cast<TEvent<int>::TEventHandler>(&B::func));
// 再次添加A对象的事件处理
// 由于没有判别唯一性,所以A对象的事件处理会被激发次
firer.MyEvent.add(&a, static_cast<TEvent<int>::TEventHandler>(&A::func));
//准备参数
TEventArg<int> args[1];
// 初始化
args[0] = 999;
// 激发事件
firer.fireEvent(args);
return 0;
}
这个粗陋的例子应该足够演示如何实现动态事件机制了,剩下的就请自行充实完成。
有空请访问:俏皮网 http://www.qpix.cn