一个原生 C++ 实现的 .NET 委托模式






4.30/5 (11投票s)
2005年4月4日
3分钟阅读

71781

473
使用 C++ 模板实现 .NET 委托模式
引言
早在 1998 年,甚至更早的时候,微软在其 Visual J++ 语言中引入了委托,作为 Sun 的 Java 订阅者模式的一种灵活替代方案,用于处理由按钮和文本框等视觉元素生成的事件。 事实上,委托一直是微软和 Sun 之间争论的焦点,因为后者从未接受将其作为 Java 标准的一部分。 有关委托和 Java 订阅者模式的冗长而内容丰富的描述,请参阅 Chris Sells 的文章 .NET 委托:一个 C# 睡前故事。
在本文中,我们使用 C++ 模板实现委托模式。在随附的演示项目中,我们提供了一个简单的事件源对象(我们称之为 Button
)的模拟,它会触发一个 Click
事件,以及一个我们称之为 ButtonContainer
的对象,该对象定义了一个点击事件处理程序并将其注册到 Button
事件源。
委托模式
.NET 委托模式由三部分组成
- 一个事件处理函数,它可以是一个实例或
static
类方法。 - 一个事件处理程序包装器,在 C# 中使用
delegate
关键字指定。 - 事件,这是一个
delegate
对象的集合,具有+=
运算符重载以及某些访问限制,因此客户端无法显式触发包含的事件。
在典型的操作场景中,希望处理类 ES
生成的事件 X
的类 EH
定义了一个处理程序,其签名适合于它希望处理的事件。 在 Windows-Forms C# 代码中,声明如下
class EH {
ES es_;
...
void EventX_Handler(Object sender, EventArgs e) { ... }
...
}
然后代码使用 EH.es_
的 X
事件注册一个类型为 EventHandler
的 delegate
对象
class EH {
ES es_;
...
void EventX_Handler(Object sender, EventArgs e) { ... }
...
public EH() {
...
es_.eventX += new EventHandler(EventX_Handler);
...
}
}
C++ 模板实现和演示项目
首先,我们创建一个用于委托的基本模板类,它具有一个双参数签名:发送者对象和指定事件参数的某种自定义类型
template<typename _Arg1, typename _Arg2> struct ClosureBase {
virtual void operator ()(_Arg1 &arg1, _Arg2 &arg2) = 0;
};
在此代码和以下代码中,我们更喜欢使用 closure 一词而不是 delegate,因为后者在 C++.NET 2005 中是保留字。正如您将看到的,它是所有具有两个参数的委托的 abstract
基类。
然后,可以使用此模板完全定义 Button
事件源类
class Button {
public:
struct ClickEventArguments {
int xCoord_, yCoord_;
ClickEventArguments(int xCoord = 0, int yCoord = 0) :
xCoord_(xCoord), yCoord_(yCoord){ }
};
// 'typedef' to make your life easier
typedef vector<ClosureBase<Button, ClickEventArguments> *> ClickEvents;
// The delegates list. Define one per event
ClickEvents clickEvents_;
void FireClickEvents(int xc, int yc) {
ClickEventArguments args(xc, yc);
for(ClickEvents::iterator it = clickEvents_.begin();
it != clickEvents_.end(); it++) {
(*(*it))(*this, args);
}
}
};
这里只介绍了与当前讨论相关代码。 如您所见,Button
类的 Click
事件是一个 ClosureBase
指针的向量。 FireClickEvents
方法遍历事件元素,调用它们的 ()
运算符。
ButtonContainer
类包含一个 Button
对象并注册一个 Click
事件处理程序
class ButtonContainer {
Button button_;
void ButtonClickHandler(Button &sender, Button::ClickEventArguments &arguments)
{
cout <<
"Delegate invoked with sender " <<
typeid(sender).name() << ", and eventArgs (" <<
arguments.xCoord_ << ", " << arguments.yCoord_ << ")" << endl;
}
public:
ButtonContainer()
{
button_.clickEvents_.push_back(CreateClosure(this,
&ButtonContainer::ButtonClickHandler));
}
};
唯一“奇怪”的事情是 ButtonContainer
构造函数中的 CreateClosure
。 可以想象,CreateClosure
是一个 ClosureBase
派生模板的生成器,该模板以事件参数和事件处理对象为参数
template<class _Ty, typename _Arg1, typename _Arg2>
class Closure : public ClosureBase<_Arg1, _Arg2> {
void (_Ty::* method_)(_Arg1 &, _Arg2 &);
_Ty *objectPtr_;
public:
Closure(_Ty *objectPtr,
void (_Ty::* method)(_Arg1 &, _Arg2 &)) : objectPtr_(objectPtr)
{ method_ = method; }
virtual ~Closure() { }
virtual void operator ()(_Arg1 &arg1, _Arg2 &arg2)
{ return (objectPtr_->*method_)(arg1, arg2); }
};
Closure
模板通过属性 method_
和 objectPtr_
将对象指针与事件处理方法签名相关联。 第一个是类方法的指针,第二个是对象指针。 然后 ()
运算符调用 ->*method_ of objectPtr_
对象指针
(objectPtr_->*method_)(arg1, arg2)
有关相关实现,请参阅 functional STL 头文件中的代码。
最后,通过 CreateClosure
生成器函数创建 Closure
对象,以使客户端代码更具可读性
template<class _Ty, typename _Arg1, typename _Arg2>
inline Closure<_Ty, _Arg1, _Arg2>
*CreateClosure(_Ty *object, void(_Ty::*method)(_Arg1 &, _Arg2 &)){
return new Closure<_Ty, _Arg1, _Arg2>(object, method);
}
在演示项目中,我们还包含一个 WindowManager
类,该类应该模拟操作系统。
在一个更完整的演示中,WindowManager
应该在后台线程中运行。 WindowManager
当然与我们的主题无关,但它实现了一个非常好的单例(称为 Meyers 单例)。
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。