- 浏览: 16750522 次
- 性别:
- 来自: 济南
-
最新评论
-
wu1236:
ef0793cd94337324b6fefc4c9474af5 ...
Android ApiDemos示例解析(87):Media->MediaPlayer -
77219634:
0127bf2236bee4dd1f632ce430f1af1 ...
本博客文章都为转载,没有任何版权! -
77219634:
0127bf2236bee4dd1f632ce430f1af1 ...
VPLEX - EMC的RAC -
77219634:
0127bf2236bee4dd1f632ce430f1af1 ...
qTip2 Show -
77219634:
0127bf2236bee4dd1f632ce430f1af1 ...
SecureCRT中文乱码、复制粘贴乱码解决办法(修改版)
委托这种机制,只有当代码写多了,才会发现它多么有用,虽然设计模式里的模板方法很好,但为了实现它而进行大量的类重载有时候可能得不偿失,然而c++本省并不支持委托,以下是一个非常强大的委托实现代码,直接可以拿来大量使用的。
http://www.codeproject.com/KB/cpp/FastDelegate.aspx
另有一中译篇:
成员函数指针与高性能的C++委托(上篇)<!-- #EndEditable --> |
<!-- #BeginEditable "2" -->撰文:Don Clugston<!-- #EndEditable --> |
引子
//为了便于理解,我强烈推荐你使用typedef关键字。 //如果不这样的话,当函数指针作为一个函数的参数传递的时候, // 程序会变得晦涩难懂。 // 这样的话,声明应如下所示: typedef float (*MyFuncPtrType)(int, char *); MyFuncPtrType my_func_ptr;
//对于使用const关键字修饰的成员函数,声明如下: float (SomeClass::*my_const_memfunc_ptr)(int, char *) const;
(x->*my_memfunc_ptr)(6, "Another Arbitrary Parameter"); //如果类在栈上,你也可以使用".*"运算符。 SomeClass y; (y.*my_memfunc_ptr)(15, "Different parameters this time");
class SomeClass { public: virtual void some_member_func(int x, char *p) { printf("In SomeClass"); }; };
public: // 如果你把下一行的注释销掉,带有 line (*)的那一行会出现错误 // virtual void some_member_func(int x, char *p) { printf("In DerivedClass"); }; };
//声明SomeClass的成员函数指针 typedef void (SomeClass::*SomeClassMFP)(int, char *); SomeClassMFP my_memfunc_ptr; my_memfunc_ptr = &DerivedClass::some_member_func; // ---- line (*) return 0; }
class Derived2: public Base2, public Base1 // 情况 (b) typedef void (Derived::* Derived_mfp)(); typedef void (Derived2::* Derived2_mfp)(); typedef void (Base1::* Base1mfp) (); typedef void (Base2::* Base2mfp) (); Derived_mfp x;
用来做例子给C++初学者看,帮助它们学习语法;或者
(pClass->*funcptr)(); };
成员函数指针与高性能的C++委托(中篇) Member Function Pointers and the Fastest Possible C++ Delegates 撰文: Don Clugston 翻译:周翔 (接上篇) 成员函数指针——为什么那么复杂? 类的成员函数和标准的 C函数有一些不同。与被显式声明的参数相似,类的成员函数有一个隐藏的参数 this ,它指向一个类的实例。根据不同的编译器, this 或者被看作内部的一个正常的参数,或者会被特别对待(比如,在 VC++中, this一般通过 ECX寄存器来传递,而普通的成员函数的参数被直接压在堆栈中)。t his 作为参数和其他普通的参数有着本质的不同,即使一个成员函数受一个普通函数的支配,在标准 C++中也没有理由使这个成员函数和其他的普通函数( ordinary function)的行为相同,因为没有 thiscall 关键字来保证它使用像普通参数一样正常的调用规则。成员函数是一回事,普通函数是另外一回事( Member functions are from Mars, ordinary functions are from Venus)。 你可能会猜测,一个成员函数指针和一个普通函数指针一样,只是一个代码指针。然而这种猜测也许是错误的。在大多数编译器中,一个成员函数指针要比一个普通的函数指针要大许多。更奇怪的是,在 Visual C++中,一个成员函数指针可以是 4、 8、 12甚至 16个字节长,这取决于它所相关的类的性质,同时也取决于编译器使用了怎样的编译设置!成员函数指针比你想象中的要复杂得多,但也不总是这样。 让我们回到二十世纪 80年代初期,那时,最古老的 C++编译器 CFront刚刚开发完成,那时 C++语言只能实现单一继承,而且成员函数指针刚被引入,它们很简单:它们就像普通的函数指针,只是附加了额外的 this 作为它们的第一个参数,你可以将一个成员函数指针转化成一个普通的函数指针,并使你能够对这个额外添加的参数产生足够的重视。 这个田园般的世界随着 CFront 2.0的问世被击得粉碎。它引入了模版和多重继承,多重继承所带来的破坏造成了成员函数指针的改变。问题在于,随着多重继承,调用之前你不知道使用哪一个父类的 this 指针,比如,你有 4个类定义如下: class A { public: virtual int Afunc() { return 2; }; }; class B { public: int Bfunc() { return 3; }; }; // C是个单一继承类,它只继承于 A class C: public A { public: int Cfunc() { return 4; }; }; // D 类使用了多重继承 class D: public A, public B { public: int Dfunc() { return 5; }; }; 假如我们建立了 C类的一个成员函数指针。在这个例子中, Afunc和 Cfunc都是 C的成员函数,所以我们的成员函数指针可以指向 Afunc或者 Cfunc。但是 Afunc需要一个 this指针指向 C::A(后面我叫它 Athis ),而 Cfunc需要一个 this指针指向 C(后面我叫它 Cthis )。编译器的设计者们为了处理这种情况使用了一个把戏( trick):他们保证了 A类在物理上保存在 C类的头部(即 C类的起始地址也就是一个 A类的一个实例的起始地址),这意味着 Athis == Cthis 。我们只需担心一个 this指针就够了,并且对于目前这种情况,所有的问题处理得还可以。 现在,假如我们建立一个 D类的成员函数指针。在这种情况下,我们的成员函数指针可以指向 Afunc、 Bfunc或 Dfunc。但是 Afunc需要一个 this指针指向 D::A,而 Bfunc需要一个 this指针指向 D::B。这时,这个把戏就不管用了,我们不可以把 A类和 B类都放在 D类的头部。所以, D类的一个成员函数指针不仅要说明要指明调用的是哪一个函数,还要指明使用哪一个 this指针。编译器知道 A类占用的空间有多大,所以它可以对 Athis增加一个 delta = sizeof(A)偏移量就可以将 Athis指针转换为 Bthis指针。 如果你使用虚拟继承( virtual inheritance),比如虚基类,情况会变得更糟,你可以不必为搞懂这是为什么太伤脑筋。就举个例子来说吧,编译器使用虚拟函数表 ( virtual function table——“ vtable”)来保存每一个虚函数、函数的地址和 virtual_delta: 将当前的 this指针转换为实际函数需要的 this指针时所要增加的位移量。 综上所述,为了支持一般形式的成员函数指针,你需要至少三条信息:函数的地址,需要增加到 this指针上的 delta位移量,和一个虚拟函数表中的索引。对于 MSVC来说,你需要第四条信息:虚拟函数表( vtable)的地址。 成员函数指针的实现 那么,编译器是怎样实现成员函数指针的呢?这里是对不同的 32、 64和 16位的编译器,对各种不同的数据类型(有 int、 void*数据指针、代码指针(比如指向静态函数的指针)、在单一( single-)继承、多重( multiple-)继承、虚拟( virtual-)继承和未知类型( unknown)的继承下的类的成员函数指针)使用 sizeof运算符计算所获得的数据:
看 着表中的数据,你是不是觉得很惊奇?你可以清楚地看到编写一段在一些环境中可以运行而在另一些编译器中不能运行的代码是很容易的。不同的编译器之间,它们 的内部实现显然是有很大差别的;事实上,我认为编译器在实现语言的其他特性上并没有这样明显的差别。对实现的细节进行研究你会发现一些奇怪的问题。 一般,编译器采取最差的,而且一直使用最普通的形式。比如对于下面这个结构: // Borland (缺省设置 ) 和 Watcom C++. struct { FunctionPointer m_func_address; int m_delta; int m_vtable_index; //如果不是虚拟继承,这个值为 0 。 } ; // Metrowerks CodeWarrior使用了稍微有些不同的方式。 //即使在不允许多重继承的 Embedded C++的模式下,它也使用这样的结构! struct { int m_delta; int m_vtable_index; // 如果不是虚拟继承,这个值为 -1。 FunctionPointer m_func_address; }; // 一个早期的 SunCC版本显然使用了另一种规则: struct { int m_vtable_index; //如果是一个非虚拟函数( non-virtual function),这个值为 0。 FunctionPointer m_func_address; //如果是一个虚拟函数( virtual function),这个值为 0。 int m_delta; }; //下面是微软的编译器在未知继承类型的情况下或者使用 /vmg选项时使用的方法: struct { FunctionPointer m_func_address; int m_delta; int m_vtordisp; int m_vtable_index; // 如果不是虚拟继承,这个值为 0 } ; // AIX (PowerPC)上 IBM的 XLC编译器: struct { FunctionPointer m_func_address; // 对 PowerPC来说是 64位 int m_vtable_index; int m_delta; int m_vtordisp; }; // GNU g++使用了一个机灵的方法来进行空间优化 struct { union { FunctionPointer m_func_address; // 其值总是 4的倍数 int m_vtable_index_2; // 其值被 2除的结果总是奇数 }; int m_delta; }; 对于几乎所有的编译器, delta和 vindex用来调整传递给函数的 this指针,比如 Borland的计算方法是: adjustedthis = *(this + vindex -1) + delta // 如果 vindex!=0 adjustedthis = this + delta // 如果 vindex=0 (其中,“ *”是提取该地址中的数值, adjustedthis是调整后的 this指针——译者注) Borland使用了一个优化方法:如果这个类是单一继承的,编译器就会知道delta和vindex的值是0,所以它就可以跳过上面的计算方法。 GNU编译器使用了一个奇怪的优化方法。可以清楚地看到,对于多重继承来说,你必须查看 vtable(虚拟函数表)以获得 voffset(虚拟函数偏移地址)来计算 this指针。当你做这些事情的时候,你可能也把函数指针保存在 vtable中。通过这些工作,编译器将 m_func_address和 m_vtable_index合二为一(即放在一个 union中),编译器区别这两个变量的方法是使函数指针( m_func_address)的值除以 2后结果为偶数,而虚拟函数表索引 (m_vtable_index_2)除以 2后结果为奇数。它们的计算方法是: adjustedthis = this + delta if (funcadr & 1) //如果是奇数 call (* ( *delta + (vindex+1)/2) + 4) else //如果是偶数 call funcadr (其中, funcadr 是函数地址除以 2得出的结果。——译者注) Inter的 Itanium编译器(但不是它们的 x86编译器)对虚拟继承( virtual inheritance)的情况也使用了 unknown_inheritance结构,所以,一个虚拟继承的指针有 20字节大小,而不是想象中的 16字节。 // Itanium, unknown 和 virtual inheritance下的情况 . struct { FunctionPointer m_func_address; //对 Itanium来说是 64位 int m_delta; int m_vtable_index; int m_vtordisp; }; 我不能保证 Comeau C++使用的是和 GNU相同的技术,也不能保证它们是否使用 short代替 int使这种虚拟函数指针的结构的大小缩小至 8个字节。最近发布的 Comeau C++版本为了兼容微软的编译器也使用了微软的编译器关键字(我想它也只是忽略这些关键字而不对它们进行实质的相关处理罢了)。 Digital Mars编译器(即最初的 Zortech C++到后来的 Symantec C++)使用了一种不同的优化方法。对单一继承类来说,一个成员函数指针仅仅是这个函数的地址。但涉及到更复杂的继承时,这个成员函数指针指向一个形式转换函数( thunk function) ,这个函数可以实现对 this指 针的必要调整并可用来调用实际的成员函数。每当涉及到多重继承的时候,每一个成员函数的指针都会有这样一个形式转换函数,这对函数调用来说是非常有效的。 但是这意味着,当使用多重继承的时候,子类的成员函数指针向基类成员函数指针的转换就会不起作用了。可见,这种编译器对编译代码的要求比其他的编译器要严 格得多。 很多嵌入式系统的编译器不允许多重继承。这样,这些编译器就避免了可能出现的问题:一个成员函数指针就是一个带有隐藏 this指针参数的普通函数指针。 微软 "smallest for class"方法的问题 微软的编译器使用了和 Borland相似的优化方法。它们都使单一继承的情况具有最优的效率。但不像 Borland,微软在缺省条件下成员函数指针省略了值为 0 的指针入口( entry),我称这种技术为“ smallest for class”方法:对单一继承类来说,一个成员函数指针仅保存了函数的地址( m_func_address),所以它有 4字节长。而对于多重继承类来说,由于用到了偏移地址( m_delta),所以它有 8字节长。对虚拟继承,会用到 12个字节。这种方法确实节省空间,但也有其它的问题。 首先,将一个成员函数指针在子类和基类之间进行转化会改变指针的大小!因此,信息是会丢失的。其次,当一个成员函数指针在它的类定义之前声明的时候,编译器必须算出要分配给这个指针多少空间,但是这样做是不安全的,因为在定义之前编译器不可能知道这个类的继承方式。对 Intel C++和早期的微软编译器来说,编译器仅仅对指针的大小进行猜测,一旦在源文件中猜测错误,你的程序会在运行时莫名其妙地崩溃。所以,微软的编译器中增加了一些保留字: __single_inheritance, __multiple_inheritance,和 __virtual_inheritance,并增设了一些编译器开关( compiler switch),如 /vmg,让所有的成员函数指针有相同的大小,而对原本个头小的成员函数指针的空余部分用 0填充。 Borland编译器也增加了一些编译器开关,但没有增加新的关键字。 Intel的编译器可以识别 Microsoft增加的那些关键字,但它在能够找到类的定义的情况下会对这些关键字不做处理。 对于 MSVC来说,编译器需要知道类的 vtable在哪儿;通常就会有一个 this指针的偏移量( vtordisp),这个值对所有这个类中的成员函数来说是不变的,但对每个类来说会是不同的。对于 MSVC,经调整过的 this指针是在原 this指针的基础上经过下面的计算得出的: if (vindex=0) //如果不是虚拟继承( _virtual_inheritance) adjustedthis = this + delta else //如果是 adjustedthis = this + delta + vtordisp + *(*(this + vtordisp) + vindex) 在虚拟继承的情况下,
vtordisp的值并不保存在
__virtual_inheritance指针中,而是在发现函数调用的代码时,编译器才将其相应的汇编代码“嵌”进去。但是对于未知类型的继承,编译器需要
|
相关推荐
网上有很多关于C++ delegate机制的文章,但都讲的是函数指针的内容,上周就C++中实现C#的delegate机制的问题研究了好几天,查看了很多相关资料,才解决了问题,现将我写的C++ delegate测试程序贴出来,希望能帮到有...
用C++做项目的时候,尤其是写客户端的时候经常会有事件回调的设计,一般的方式是使用虚函数表,用一个虚基类包含...但这种方式和C++11的lamda不兼容,为了更方便的实现事件回调机制,使用delegate是很不错的一种方式。
在C++编程中,委托(Delegate)是一种设计模式,它允许将函数或者方法作为参数传递,增强了代码的灵活性和可扩展性。MyGUI是一个流行的2D图形用户界面库,它为开发者提供了丰富的组件和功能。在MyGUI中,委托机制是...
下面我们将详细探讨如何在C++中实现委托的代码。 首先,理解委托的基本思想:一个委托对象可以持有对另一个函数或成员函数的引用,并能够在适当的时候调用它。为了在C++中实现这个概念,我们可以使用函数指针、仿...
本文将深入讲解“简单的Delegate实现”,并以实际代码为例,帮助你理解这一概念。 Delegate在iOS中的主要作用是传递消息或事件。例如,UITableView需要知道何时应该加载数据,如何显示单元格,用户点击了哪个单元格...
在本示例中,“Delegate实现传值”主要探讨的是如何利用Delegate在两个界面上进行数据传递,尤其是从第二个界面向第一个界面传递数据。 在Windows Forms或WPF等UI开发中,通常我们需要在不同的界面或控件之间传递...
本文实例讲述了C#使用委托(delegate)实现在两个form之间传递数据的方法。分享给大家供大家参考。具体分析如下: 关于Delegate【代理、委托】是C#中一个非常重要的概念,向前可以推演到C++的指针,向后可以延续到匿名...
在C++编程中,委托(Delegate)是一种设计模式,它允许将函数或者方法作为参数传递,增强了代码的灵活性和可扩展性。MyGUI是一个流行的2D图形用户界面库,它提供了一种基于C++的委托实现,使得用户可以方便地在事件...
这是我在VC++日常开发中收集的一些经常用到的类库,每一款都可以轻松重用,这些类库适用于vs2003及以上的开发环境,其中 Thread 和 Delegate 是从C#中得到的灵感,还有一些是在使用开源代码或其它语言时得到的启发和...
在C++编程中,委托(Delegate)是一种设计模式,它允许将函数或方法作为参数传递,使得函数可以作为另一种函数的输入,增强了代码的灵活性和可扩展性。在这个主题中,我们将深入探讨Don Clugston大神实现的高效C++...
### C++中的委托和事件实现 在C#中,委托是一种引用类型,用于封装一个或多个方法。事件是委托的特殊形式,通常用于实现观察者模式。在C++中,我们可以利用`std::function`和`std::bind`来模拟这些概念。 首先,...
本文将深入探讨如何在C++中实现C#事件机制的关键元素,并介绍与之相关的函数指针和尾随参数的概念。 首先,让我们了解C#中的事件和委托。在C#中,事件是一种特殊的委托(delegate)类型,用于封装多个方法,这些...
本示例展示了如何在C#和C++之间实现互操作性,即C#调用C++编写的动态链接库(DLL)函数,同时C++也能调用C#的方法。这主要依赖于.NET框架提供的互操作特性,如C++/CLI(C++的.NET版本)和P/Invoke(Platform Invoke...
总结起来,iOS中的Delegate是实现对象间通信的有效手段,它简化了代码结构,提高了代码的复用性。通过遵循协议并在适当的地方设置和调用Delegate方法,开发者可以在不同的对象之间轻松地传递数据和响应事件。在实际...
CLI/C++,也被称为Managed C++或C++/CLI,允许开发者使用C++语法编写托管代码,同时也能调用非托管(本地)C++代码。这种混合编程模式使得.NET应用程序可以利用C++的强大功能和灵活性,同时享受.NET平台的便利。 ...
在提供的压缩包文件"Delegate"中,可能包含了实现以上过程的示例代码,包括定义协议、声明和设置委托、以及实际调用委托方法的示例。通过查看这些源码和注释,你可以更深入地理解Objective-C中的委托机制,并学习...
在Objective-C中,`delegate`是一种关键的编程模式,用于实现对象间的通信。本文将深入探讨如何在iPhone编程中使用`delegate`,并以一个名为"MyDelegateSample"的示例项目来说明。 首先,`delegate`是Objective-C中...
在跨语言编程中,有时我们需要在C#代码中定义的函数能够在C++环境中被调用,这种情况通常通过回调函数来实现。本示例"CallbackTestCpp"将详细讲解如何在C#中定义一个函数,然后在C++项目中注册并执行这个函数。这...
// 实现调用照相机的代码 } - (void)toolBarDidRequestKeyboardToggle:(WBComposeToolBar *)toolBar { // 实现切换键盘的代码 } @end ``` 第四步:设置委托(Setting the Delegate) 在适当的地方(如视图加载...