C++:关于委托类

转自:http://blog.csdn.net/dadalan/article/details/4041931。vs2010已经支持function/bind,能很好实现委托。

[说明]

本文不仅介绍了C++语言应用非常好的一种方法(我甚至觉得应该将它归结为一种设计模式),而且也是对C#语言中委托特性底层实现的一个很好的说明。

阅读本文,你应当对委托的概念有所了解;在讨论委托是实现时,你应当对标准模板库(STL)中的list容器以及迭代器(iterator)有所了解。

在这篇文章中,暂不讨论类成员函数。

1.C#中的委托

你如果对C#语言比较了解的话,就应该会知道C#语言中有一个很好的特性,那就是委托。它能够大大简化在某些特定的场合调用多个相同形式函数的处理。特别是在像Windows程序中,用委托响应消息十分方便。举一个常见的例子。现在的Windows应用程序框架都比较复杂,一个应用程序可能由许多部件组成,很多时候它们都需要响应同一个消息。如在一个MDI中,多个子窗体都要响应主窗体的WM_QUIT消息。很多时候,我们并不需要像MFC那样将所有的处理都封装在类中,我们需要一种简单易用的方法,直观地解决这个问题。这篇文章为你提供了这样的方法。

在具体介绍这篇文章之前,让我们先看看在C#中是怎样使用委托的。首先,在像处理Windows消息这样的操作时,被通知的对象都以一种固定的格式来接受消息。在C#中,处理消息的委托被定义为如下格式:

public delegate void WinEventHandler(object sender,
EventArgs arg);

假设在一个MDI中,主窗体由MainFrame实现。我们在主窗体定义一个专门用于处理WM_QUIT消息:

public event WinEventHandler OnQuit;

在上面的声明中,public表明该委托可以在外部访问(注册/删除),event关键字表明这是一个事件,只能在类内部调用,外部不能直接触发它。在MainFrame的窗体过程函数中,WM_QUIT消息是这样被分发的:

protected virtual void WndProc(Message msg,object sender,EventArgs arg)

{

......

switch(msg)

{

......

case WM_QUIT:

OnQuit(sender,arg);

break;

......

}

}

那么,MainFrame的子窗体如何响应主窗体的WM_QUIT消息呢?首先,你要实现子窗体Child1处理该消息的函数,它的声明形式要跟WinEventHandler委托相同:

//In Child1 Class

protected void Child1_On_Quit(object sd, EventArgs ags)

{

this.SaveAll();       
file://做些善后工作,如保存当前信息等

……

}

在子窗体被初始化时向MainFrame的OnQuit委托注册这个函数。如果MainFrame是Child1的父窗体,那么其实现可能是这样的:

parent.OnQuit += new
WinEventHandler(this.Child1_On_Quit);

这样,当MainFrame收到WM_QUIT消息时,调用OnQuit委托,同时Child1.Child1_On_Quit也被调用,从而实现消息传递。当然,也有子窗体不再需要响应主窗体的WM_QUIT消息的时候。我们可以通过下面的方式从MainFrame的OnQuit委托中注销它:

parent.OnQuit -= new
WinEventHandler(this.Child1_On_Quit);

这一步也是很必要的。如果Child1先于主窗体MainFrame被摧毁,而Child1_On_Quit没有从MainFrame.OnQuit委托中注销,则主窗体收到WM_QUIT消息时调用OnQuit委托,它又顺序调用到Child1.Child1_On_Quit,则可能引发空引用异常了。[详细介绍,请参见《IL代码底层运行机制:函数相关]

委托可以接受多个实例方法,因此你可以向一个委托注册多个方法。实际上,委托包含了一个方法引用的列表,当委托被调用时,它将顺序调用其列表中的方法引用。这一点我还会在后面详细说明。

2.在C++中实现委托

我们知道了C#中委托的原理,是不是也可以在C++中实现呢?答案是肯定的。不同的是C#在语言级别提供了对委托的支持,而C++没有,它需要我们对委托进行定制。这样,每种不同形式的委托都要有不同的实现,灵活性大打折扣。幸运的是,作者已经提供了一个名为delegate.exe的实用小工具,它可以帮我们实现由委托声明生成实际代码。其用法将在后面详细介绍。

现在,我们主要考虑的是如何来实现我们自己定制的委托。前面我已经简单介绍了一个委托应当具备的因素:保存方法引用(在C++中是指针,但在这里我还是习惯称之为引用)的列表,添加/删除方法引用,以及最重要的调用例程。有多中方法可以实现这些操作,这里我们采用类来实现。

第一个要考虑的是如何来保存方法引用。因为方法引用(指针)实际上是一个32位无符号整数,因此我也采用无符号整型来存储方法引用(指针)。这里,我定义了这种数据类型:typedef
unsigned int NativePtr。在接受

第二是声明这个委托的形式。在这个例子中,我采用void
Handle(char *str)的形式作为示例。我们定义这种函数指针类型typedef void (* Handler)(char
*str)以供函数调用时,作为由无符号整形向函数指针的转换类型。

第三个要考虑的是怎样实现多个方法引用(指针)的存储。最简单的方法是使用STL中的list列表容器存储。list模板类为我们提供了一组非常方便的列表存取操作方法,它提供的迭代器使我们能够很容易地使用它。在CDelegate类中,我定义了用于存储函数引用的字段ftns:list
<NativePtr> ftns.

第四是实现添加/删除函数引用。这里两个操作分别由AddFunction/RemoveFunction来实现。在这里,有一个问题是我们接受什么样的参数类型,怎样接受。毫无疑问,它要接受的是前面定义的Handler类型。但我们已使用32为无符号整型来保存其信息,因此,为了简单起见,AddFunction/RemoveFunction函数的参数为void
* 类型,这样它可以接受许多类型的参数。关于使用什么样的类型作为AddFunction/RemoveFunction的参数这一点,我想可能还要详细讨论一下,究竟是前面定义的Handler类型还是void
*类型。应该说两种类型都有其优缺点,关键是看我们在什么时候应用。当然,使用我们定制的委托类型(也即前面定义的Handler类型)作为其参数类型可以让编译器为我们做必要的语法检查,以防止不匹配的参数被传替。这就要看实际情况了。

当然,重载 += 和 - = 操作符是必须的了。

最后,也是最重要的是实现我们的Invoke方法。我们对Invoke方法有要求,它必须和我们定制的委托类型是一致的。同时,我们也需要重载()操作符号,以方便我们像一般函数那样调用它。

下面我给出类的定义部分:

#include <list>

using namespace std;

/*定义一个无符号型32位整数类型,该类型用于存储函数引用(指针)*/

typedef unsigned int NativePtr;

/*定义函数原型,返回值须为空,参数根据需要可改变*/

typedef void (* Handler)(char *);

class CDelegate

{

private:

/* 函数列表,被添加的函数引用(指针)都放在该列表中 */

list<NativePtr> ftns;

public:

/*添加函数引用(指针)*/

void AddFunction(void
*);

/*删除函数引用(指针)*/

void RemoveFunction(void *);

/*调用例程:最重要的部分,实现对列表中的函数逐个调用*/

int Invoke(char *);

/*运算符重载AddFunction方法*/

void operator += (void *);

/*运算符重载RemoveFunction方法*/

void operator -= (void *);

/*运算符重载Invoke方法*/

int operator ()(char *);

};

下面我给出各个方法实现的代码。

#include "Delegate.h"

void CDelegate::AddFunction(void *ftn)

{

NativePtr np=(NativePtr)ftn;

ftns.push_back(np);

}

AddFunction函数接受类型为void * 的参数,然后将这个参数强制转换为NativePtr(unsigned
int)类型,存放于ftns列表中。注意,这个数值从列表的尾部插入,以实现FIFO。

void CDelegate::RemoveFunction(void *ftn)

{

NativePtr np=(NativePtr)ftn;

ftns.remove(np);

}

RemoveFunction函数接受类型为void * 的参数,然后将这个参数强制转换为NativePtr ( unsigned int
) 类型,再从ftns中删除与它的值相同的元素。

void CDelegate::operator += (void *ftn)

{

this->AddFunction(ftn);

}

+=操作符重载AddFunction方法。

void CDelegate::operator -= (void *ftn)

{

this->RemoveFunction(ftn);

}

-=操作符RemoveFunction方法。

int CDelegate::Invoke(char * pch)

{

Handler handle;

list<NativePtr>::iterator
itr=ftns.begin();

try

{

for(;itr!=ftns.end();itr++)

{

handle=(Handler)*itr;

handle(pch);

}

}

catch(char *)

{

return 0;

}

return 1;

}

使用list模板类提供的迭代器,遍历ftns中的每个元素,顺次将元素转化为定制的函数引用(指针)类型,并调用其所对于的函数。这里要求委托返回值必须为空。如有异常,则Invoke返回0值。

int CDelegate::operator ()(char *pch)

{

return Invoke(pch);

}

()操作符重载Invoke方法。

可以看到,我们实现的这个委托类其实很简单。将添加的函数引用(指针)添加到一个列表中;当委托被调用时,将列表中的函数引用逐个取出并调用。C#中的委托的实现也是如此;它对委托的处理,程序生成器的脚色是由编译器扮演的。其实如果你对由C#编译器生成的IL代码进行剖析,每个C#委托声明也都是被转化为继承自某个支持类似功能的类处理的。同时,也正是由于委托管理着多个方法的调用,它不能处理它们的返回值,所以委托要求被委托的函数不能具有返回值。

下面是运行示例:

#include "Delegate.h"

#include <iostream>

#include <windows.h>

void Say1(char *s)

{

cout<<"In Function
Say1:   ";

cout<<s<<endl;

}

void Say2(char *s)

{

cout<<"In Function
Say2:   ";

cout<<s<<endl;

}

void STHeoaie(char *s)

{

MessageBox(NULL,s,"Delegate",MB_OK);

}

void main()

{

CDelegate dlg;

dlg.AddFunction(Say1);

dlg.AddFunction(Say2);

dlg+=STHeoaie;

int rs=dlg.Invoke("Hello,World!");

if(!rs)
cout<<"Failed."<<endl;

/*

第一次调用结果:

*/

dlg-=Say2;

rs=dlg("The second invoking by
CDelegate!");

file://等同于dlg. Invoke("The second
invoking by CDelegate!")

if(!rs)
cout<<"Failed."<<endl;

/*

第二次调用:

*/

dlg-=Say1;

dlg-=STHeoaie;

rs=dlg.Invoke("The Third invoking by
CDelegate!");

if(!rs)
cout<<"Failed."<<endl;

/*

第三次调用,没有任何输出,因为已注销所以方法:

*/

}

3.关于实用小工具delegate.exe

为了解决在定制委托时的不灵活性,我特意编写了这个小工具,它能够方便地将委托声明转化为如上面所述的代码。下面是其基本用法。

在你的某个头文件中,如test.h,以__delegate 关键字声明一个委托:

__delegate void WinHandler ( HWND hwnd
,UINT message ,WPARAM wParam,LPARAM lParam);

然后转到命令行模式,进入test.h的目录,键如如下命令:

delegate.exe test.h /out test.hxx

它将生成test.hxx文件。你可以向你的源程序中包含这个文件,以使用你所定义的委托。如,可以是这样:#include “test.hxx”

你可以在一个文件里定义多个委托,也可以在多个文件里定义多个委托.但是你只能指定一个的输出文件.如果你没有用/out 选项指定输出文件,则默认输出为delegate.h。如:

delegate.exe test.h test1.h test2.h /out
test.hxx

使用/help选项得到帮助信息,使用/version选项得到版本信息.

注意:最新的Visual C++ .Net 版本已经支持同名关键字 __delegate,这是微软公司为了将Visual
C++向.net移植而添加的新关键字,只有 Visual C++ .Net 支持,其他如Visual C++ 6.0、Borland C++
Builder 、GNU
C++ 等都不支持。但两个完全没有联系.幸运的是,delegate小工具支持/keyword 选项,它可以指定你自己定义的关键字,如__delegate__。

C++:关于委托类,布布扣,bubuko.com

时间: 2024-12-22 22:26:18

C++:关于委托类的相关文章

关于C#委托应用与总结

什么是委托? 官方解释 委托是一种定义方法签名的类型.当实例化委托时,您可以将其实例与任何具有兼容签名的方法相关联.您可以通过委托实例调用方法.   个人理解 委托是一个持有一个或多个方法的对象,并且该对象可以被执行,可以被传递 使用方法   首先创建一个类或多个类并且创建方法有无参数都可以 使用关键字 delegat 创建委托 实例化类 使用委托类创建委托对象并且为对象赋值 赋值内容为类的方法用对象名和"+"这个符号可以持有多个方法 调用委托  调用方式与函数类同 笔者在这里用案例写

C#学习(10):委托

1.疑问: 1.委托是什么? 2.为什么需要委托? 3.委托能用来做什么? 4.如何自定义委托? 5..NET默认的委托类型有哪几种? 6.怎样使用委托? 7.多播委托是什么? 8什么是泛型委托? 9.什么是匿名方法? 10.委托是否可以回调实例方法? 11.Lambda表达式是什么? 12.Lambda表达式怎么传参? 13.Lambda多行代码怎么写? 14.什么是闭包? 2.解答: 1.委托是什么? 本题主要考察委托的概念:委托是寻址的.NET版本.在C++中,函数指针只不过是一个指向内存

[转载]C#深入分析委托与事件

原文出处: 作者:风尘浪子 原文链接:http://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html 同类链接:http://www.cnblogs.com/SkySoot/archive/2012/04/05/2433639.html 引言 本篇文章将为你介绍一下 Delegate 的使用方式,逐渐揭开 C# 当中事件(Event)的由来,它能使处理委托类型的过程变得更加简单.还将为您解释委托的协变与逆变,以及如何使用 Deleg

[转载]C#委托和事件(Delegate、Event、EventHandler、EventArgs)

原文链接:http://blog.csdn.net/zwj7612356/article/details/8272520 14.1.委托 当要把方法作为实参传送给其他方法的形参时,形参需要使用委托.委托是一个类型,是一个函数指针类型,这个类型将该委托的实例化对象所能指向的函数的细节封装起来了,即规定了所能指向的函数的签名,也就是限制了所能指向的函数的参数和返回值.当实例化委托的时候,委托对象会指向某一个匹配的函数,实质就是将函数的地址赋值给了该委托的对象,然后就可以通过该委托对象来调用所指向的函

[读书笔记]C#学习笔记二: 委托和事件的用法及不同.

前言:  C#委托是什么 c#中的委托可以理解为函数的一个包装, 它使得C#中的函数可以作为参数来被传递, 这在作用上相当于C++中的函数指针. C++用函数指针获取函数的入口地址, 然后通过这个指针来实现对函数的操作. 委托的定义和方法的定义类似, 只是在定义的前面多了一个delegate关键字. 正文: 委托可以被视为一个更高级的指针,它不仅仅能把地址传指向另一个函数,而且还能传递参数,返回值等多个信息. 系统还为委托对象自动生成了同步,异步的调用方式,开发人员使用BeginInvoke,E

C#综合揭秘——深入分析委托与事件

本篇文章将为你介绍一下 Delegate 的使用方式,逐渐揭开 C# 当中事件(Event)的由来,它能使处理委托类型的过程变得更加简单.还将为您解释委托的协变与逆变,以及如何使用 Delegate 使 Observer(观察者)模式的使用变得更加简单.在事件的介绍上,会讲述事件的使用方式,并以ASP.NET的用户控件为例子,介绍一下自定义事件的使用.最后一节,将介绍Predicate<T>.Action<T>.Func<T,TResult>多种泛型委托的使用和Lamb

重构笔记——隐藏“委托关系”

本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/43769929         在上一篇文章中介绍了"将类内联化".本文将介绍"隐藏委托关系"这种重构手法.         下面让我们来学习这种重构手法吧. 开门见山         发现:客户通过一个委托关系来调用另一个对象. 解决:在服务类上建立客户所需的所有函数,用以隐藏委托关系. 动机 我们都知道,"封装&q

C#委托及事件处理机制浅析

事件可以理解为某个对象所发出的消息,以通知特定动作(行为)的发生或状态的改变.行为的发生可能是来自用户交互,如鼠标点击:也可能源自其它的程序逻辑.在这里,触发事件的对象被称为事件(消息)发出者(sender),捕获和响应事件的对象被称作事件接收者. 在事件(消息)通讯中,负责事件发起的类对象并不知道哪个对象或方法会接收和处理(handle)这一事件.这就需要一个中介者(类似指针处理的方式),在事件发起者与接收者之间建立关联.在.NET Framework中,定义了一个特殊的类型(delegate

重构改善既有代码设计--重构手法14:Hide Delegate (隐藏委托关系)

客户通过一个委托类来调用另一个对象.在服务类上建立客户所需的所有函数,用以隐藏委托关系. 动机:封装即使不是对象的最关机特性,也是最关机特性之一.“封装”意味着每个对象都应该少了解系统的其他部分.如此以来,一旦发生变化,需要了解这一变化的就比较少---这会使系统比较容易进行. 任何学过对象技术的人都知道:虽然Java将字段声明为public,但你还是应该隐藏对象的字段.随着经验日渐丰富,你会发现,有更多可以(值得)封装的东西.如果某个客户需要通过服务对象的字段得到另一个对象,然后调用后者的函数,