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++:关于委托类的更多相关文章
- kotlin 委托类的初始化函数
import java.beans.AppletInitializer import kotlin.reflect.KProperty fun main(arg: Array<String> ...
- 并发编程概述 委托(delegate) 事件(event) .net core 2.0 event bus 一个简单的基于内存事件总线实现 .net core 基于NPOI 的excel导出类,支持自定义导出哪些字段 基于Ace Admin 的菜单栏实现 第五节:SignalR大杂烩(与MVC融合、全局的几个配置、跨域的应用、C/S程序充当Client和Server)
并发编程概述 前言 说实话,在我软件开发的头两年几乎不考虑并发编程,请求与响应把业务逻辑尽快完成一个星期的任务能两天完成绝不拖三天(剩下时间各种浪),根本不会考虑性能问题(能接受范围内).但随着工 ...
- Kotlin 委托(1)类委托、变量委托注意事项
1.官方文档 英文: https://kotlinlang.org/docs/reference/delegation.html https://kotlinlang.org/docs/referen ...
- C# 委托Delegate(一) 基础介绍&用法
本文是根据书本&网络 前人总结的. 1. 前言 定义&介绍: 委托Delegate是一个类,定义了方法的类型, 使得可以将方法当做另一个方法的参数来进行传递,这种将方法动态地赋给参数的 ...
- 匹夫细说C#:庖丁解牛聊委托,那些编译器藏的和U3D给的
0x00 前言 由于工作繁忙所以距离上一篇博客已经过去一个多月的时间了,因此决心这个周末无论如何也得写点东西出来,既是总结也是分享.那么本文主要的内容集中在了委托的使用以及内部结构(当然还有事件了,但 ...
- [转载]C#深入分析委托与事件
原文出处: 作者:风尘浪子 原文链接:http://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html 同类链接:http://www.c ...
- [转载]C#委托和事件(Delegate、Event、EventHandler、EventArgs)
原文链接:http://blog.csdn.net/zwj7612356/article/details/8272520 14.1.委托 当要把方法作为实参传送给其他方法的形参时,形参需要使用委托.委 ...
- 委托、Lambda表达式和事件
1.1 引用方法 委托是寻址方法的 .NET 版本.委托是类型安全的类.它定义了返回类型和参数的类型.委托类不仅包含对方法的引用,也可以包含对多个方法的引用. Lambda 表达式 ...
- 自定义委托类型 - .Net自带委托类型
委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递. 与其他的类不同,委托类具有一个签名,并且它只能对与其签名匹配的方法进行引用. 一.自定义委托类型 1.语法结构:访问修 ...
随机推荐
- 论文阅读 | CornerNet:Detecting Objects as Paired Keypoints
论文地址:https://arxiv.org/abs/1808.01244v1 论文代码:https://github.com/umich-vl/CornerNet 概述 CornerNet是一篇发表 ...
- 《Paxos Made Simple》翻译(转)
1 Introduction 可能是因为之前的描述对大多数读者来说太过Greek了,Paxos作为一种实现容错的分布式系统的算法被认为是难以理解的.但事实上,它可能是最简单,最显而易见的分布式算法了. ...
- 关于DES加密之选择更新版
数据加密算法(Data Encryption Algorithm,DEA)是一种对称加密算法,很可能是使用最广泛的密钥系统,特别是在保护金融数据的安全中,最初开发的DEA是嵌入硬件中的.通常,自动取款 ...
- 前端+php实现概率抽奖
转前端之后,后台工程师大大跑路了只能兼任他的位置写点东西了 前端+后台抽奖代码网上一大堆,引用一位仁兄前面的代码(比较懒抱歉,后面数据处理,奖项判断是否抽完我将会标红,因为前面的代码网上太多了都能找到 ...
- Vector bit-select and part-select addressing verilog片选写法
大端 m m[ a +: b ] == m[ (a+b-1) : a ] m[ a -: b ] == m[ a : (a-b+1) ] 小端 n n[ a +: b ] == n[ a : (a+b ...
- vim安装与配置
vim 8.0 安装 git clone https://github.com/vim/vim.git sudo apt-get install libncurses5-dev # vim依赖一个n ...
- setSelectionRange方法解决光标错位问题
inputElement.setSelectionRange(value.length, value.length);
- [JAVA]流控及超流控后的延迟处理
流控检查(每半秒累计,因此最小留空阀值只能做到每秒2条): import java.text.SimpleDateFormat; import java.util.Date; import java. ...
- 我的Python升级打怪之路【二】:Python的基本数据类型及操作
基本数据类型 1.数字 int(整型) 在32位机器上,整数的位数是32位,取值范围是-2**31~2--31-1 在64位系统上,整数的位数是64位,取值范围是-2**63~2**63-1 clas ...
- django notes 二:URL dispatcher
一般在 settings.py 中会有一个 ROOT_URLCONF ,请求到来时 django 会从 ROOT_URLCONF 指向的文件中查找 urlpatterns 变量配置的路由. url ...