65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.30/5 (11投票s)

2005年4月4日

3分钟阅读

viewsIcon

71781

downloadIcon

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 事件注册一个类型为 EventHandlerdelegate 对象

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 单例)。

许可证

本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.