成员函数指针和最快的C++委托 ( 积分: 0 )

  • 主题发起人 主题发起人 lich
  • 开始时间 开始时间
L

lich

Unregistered / Unconfirmed
GUEST, unregistred user!
-----------------------------------------------------------
在我搜索的过程中,我找不到很多关于成员函数指针的除了编译时的应用外的出色的应用例子。因为他们的复杂性,他们对语言的增值并不大。它最终难逃这样的一个结论:C++的成员函数指针的设计是存在缺陷的。
在写这篇文章的时候,我有一个主要的观点:C++标准允许你在成员函数指针之间进行转换,但转换成功后不允许你调用他们,多么可笑!可笑在下面三个原因。第一,转换在很多流行的编译器上不总是能够工作(也就是说,转换是标准的,但不是可移植的)。第二,在所有的编译器上,如果转换是成功的,调用转换后的成员函数指针的结果将符合你所期望的:(标准中)没有必要将其归类为“无定义行为”。(祈祷?是可移植的,但不是标准的!)第三,允许转化但不允许祈祷(invocation)是一无用处的。如果转换和祈祷都是可能的,那么有效的委托是很容易实现的。这将给语言带来巨大的价值。
-----------------------------------------------------------


成员函数指针和最快的C++委托
陆其明 译
原文:http://www.codeproject.com/cpp/FastDelegate.asp
作者:Don Clugston

介绍
标准C++并没有真正的面向对象的函数指针。这是很遗憾的,因为面向对象的函数指针(有时也称为委托)已经在其他语言中被证明了它的价值。在Delphi(面向对象的Pascal语言)中,面向对象的函数指针是Borland公司构建VCL(可视化组件库)的基础。最近,C#为了显示其语言本身的成功,也在力推委托(delegate)的概念。对于很多应用程序来说,委托简化了一些使用松耦合对象构建的设计模式的使用(如观察者模式Observer、策略模式Strategy、状态模式State,注:这些模式出自四巨头写的《设计模式:可复用面向对象软件的基础》一书)。毫无疑问,面向对象的函数指针对于标准C++也是很有用的。
C++没有委托的概念,它只提供成员函数指针。大部分C++程序员从来没有使用过成员函数指针,而且他们有很好的理由。因为成员函数指针有很奇异的语法(如->* ,.*),一般程序员很难确切理解他们的意思,况且大部分事情可以通过其他方式来实现。这里有一点误解:事实上,对于编译器来说,实现一个适当的委托比实现成员函数指针要容易得多。
本文,我将为大家揭开成员函数指针的神秘面纱。首先我会介绍成员函数指针的语法和特性,然后我再解释成员函数指针是如何在一般的编译器里被实现的,还有编译器如何高效地实现委托。最后,我将展示我是如何使用这些关于成员函数指针的鲜为人知的知识,来实现在大部分C++编译器上能有很高效率的委托。打个比方说,在Visual C++(6.0或.NET或.NET 2003)调用一个单目标的委托,将仅仅需要产生两行汇编代码!

函数指针
我们先来看一下函数指针。在C中,乃至后来的C++中,一个指向带一个int参数和一个char *参数、返回一个float值的函数指针(暂定为my_func_ptr)可能被声明如下:
float (*my_func_ptr)(int, char *);
// To make it more understandable, I strongly recommend that you use a typedef.
// Things can get particularly confusing when
// the function pointer is a parameter to a function.
// The declaration would then
look like this:
typedef float (*MyFuncPtrType)(int, char *);
MyFuncPtrType my_func_ptr;
注意,不同的参数组合的函数指针,类型是不一样的。在微软的VC中,不同的函数调用协议(calling conventions)也会导致函数指针类型的不同。这些函数调用协议包括__cdecl、__stdcall和 __fastcall。你可以如下使用函数指针指向一个函数float some_func(int, char *):
my_func_ptr = some_func;
当你以后想调用这个函数时,可以这么做:
(*my_func_ptr)(7, "Arbitrary String");
函数指针的类型之间是可以相互转换的。但将函数指针转型为void *却是不允许的。其它的操作这里就不作介绍了。一个函数指针可以赋值为0,来表示这是一个空指针。函数指针还可以进行一系列比较操作(可以使用比较符(==, !=, <, >, <=, >=))。你可以通过==0或隐式转型为bool来判断一个函数指针是否为空指针。有趣的是,函数指针可以被当作无类型模板参数来使用。这跟类型参数有着根本的区别,跟整体的无类型参数也是不同的。它根据名字来被实例化,而不是根据类型或值。基于名字的模板参数不被任何编译器所支持,甚至也不被所有其他的支持部分模板专用的模板所支持。
在C中,函数指针最通常地被用作一些库函数(如qsort)的参数,Windows函数的回调等。当然,函数指针还有很多其它的应用。函数指针的实现很简单:他们就是代码的指针,他们保存了汇编语言函数的起始地址。不同的函数指针类型的存在,仅仅是为了保证函数在被调用时使用正确的调用协议。

成员函数指针
在C++程序中,大多数函数都是成员函数。也就是说,他们是一个类的一部分。使用一个普通的函数指针指向一个成员函数是不允许的。正确的做法是,你必须使用一个成员函数指针。一个指向SomeClass类的一个成员函数的成员函数指针可以被声明如下(函数参数跟前面一样):
float (SomeClass::*my_memfunc_ptr)(int, char *);
// For const member functions, it's declared like this:
float (SomeClass::*my_const_memfunc_ptr)(int, char *) const;
注意到我们在上面的声明中用到了(::*) ,也就是SomeClass成为了声明的一部分。成员函数指针有一个讨厌的限制:他们只能用来指向一个类内的成员函数。不同的参数组合要使用不同类型的成员函数指针。在MSVC中,不同的函数调用协议(包括__cdecl、__stdcall、__fastcall和__thiscall,__thiscall是默认值。有趣的是,__thiscall关键字没有文档说明。有时如果你显式地使用它,会得到一个错误信息,指示这个关键字是为将来使用而保留的)也要使用不同的函数指针类型。如果你使用成员函数指针,建议你使用typedef来避免不必要的混淆。
可以如下指向函数float SomeClass::some_member_func(int, char *) :
my_memfunc_ptr = &amp;SomeClass::some_member_func;
大部分编译器(比如MSVC)允许你省略&amp;,但有一些(比如GNU G++)不允许省略。因此如果你想你写的代码具有良好的移植性,请保留&amp;。调用一个成员函数指针,你需要提供SomeClass的实例,而且你必须使用特殊的操作符->*。这个操作符的优先级很低,因此要用括号括起来:
SomeClass *x = new SomeClass;
(x->*my_memfunc_ptr)(6, "Another Arbitrary Parameter");
// You can also use the .* operator if your class is on the stack.
SomeClass y;
(y.*my_memfunc_ptr)(15, "Different parameters this time");
不要因为这种语法而指责我,看起来,c++的设计者很热衷于标点符号。
为了支持成员函数指针,c++比c语言增加了3个特殊的操作符。::*被用作声明指针,->* 和.* 被用作调用指针所指向的函数。看起来,这个语言中晦涩的、很少被使用的特性已经得到了非常的关注。(你甚至可以重载->*操作符,尽管你为什么这么做的原因已经超过了我的想象。我只是知道有这种用法而已。)
一个成员函数指针可以被设为0,可以提供==和!=操作,但仅限在同一个类的成员函数指针之间。任何成员函数指针都可以跟0比较,以此来判断是不是空指针。与一般的函数指针不同,不等比较符(<, >, <=, >=)不能用在成员函数指针之间。跟函数指针一样,成员函数指针可以被用作无类型模板参数(编译器可能要打补丁)。

诡异的成员函数指针
成员函数指针还有一些诡异的地方。首先,你不能用一个成员函数指针指向一个静态(static)的成员函数,而必须使用一个一般的函数指针。(因此,“成员函数指针”这个名字有点误导:事实上他们只是“非静态成员函数指针”。)其次,当处理派生类的时候,会有几处让你吃惊的地方。例如,下面的代码(不要改动注释部分)将在MSVC上编译通过:
class SomeClass {
public:
virtual void some_member_func(int x, char *p) {
printf("In SomeClass");
};
};
class DerivedClass : public SomeClass {
public:
// If you uncomment the next line, the code at line (*) will fail!
// virtual void some_member_func(int x, char *p) { printf("In DerivedClass");
};
};
int main() {
// Declare a member function pointer for SomeClass
typedef void (SomeClass::*SomeClassMFP)(int, char *);
SomeClassMFP my_memfunc_ptr;
my_memfunc_ptr = &amp;DerivedClass::some_member_func;
// ---- line (*)
}
很奇怪的是,&amp;DerivedClass::some_member_func是SomeClass类的一个成员函数指针,但它不是DerivedClass类的成员。(一些编译器的行为有些细小的差别:比如对于Digital Mars C++来说,这种情况下的&amp;DerivedClass::some_member_func是无定义的。)但是,如果DerivedClass类重新实现虚函数some_member_func,上面的代码将无法编译通过,因为&amp;DerivedClass::some_member_func现在变成了一个DerivedClass类的成员函数指针。
成员函数指针之间的转换是一个异常黑暗的区域。在C++标准化进程中,关于是否能够将一个成员函数指针从一个类转化到他的基类或者派生类的一个成员函数指针,以及是否能在两个不相关的类之间实现转换,存在着很多的争论。然而到标准委员会做出决定的时候,一些编译器开发商早已经有了他们自己的实现,他们对这些问题给出了自己的答案。根据标准5.2.10/9部分,你可以使用reinterpret_cast来帮助在一个类的一个成员函数指针中存储另外一个不相关类的一个成员函数。执行这种转化后的成员函数的结果是无法预料的。你唯一能做的,就只有将成员函数指针仍然转换成它原来的类型(原来是哪个类的还是转化回哪个类)。我会在下文继续讨论这个问题,因为这是个标准与实际的编译器相去甚远的部分。
在一些编译器中,诡异的事情甚至会在基类和派生类的成员函数指针之间转换时发生。当你使用了多重继承,使用reinterpret_cast将成员函数指针从一个派生类转换到一个基类可能不能被编译通过,这取决于你的派生类在声明时安排基类的顺序!这里有个例子:
class Derived: public Base1, public Base2 // case (a)
class Derived2: public Base2, public Base1 // case (b)
typedef void (Derived::* Derived_mfp)();
typedef void (Derived2::* Derived2_mfp)();
typedef void (Base1::* Base1mfp) ();
typedef void (Base2::* Base2mfp) ();
Derived_mfp x;
对于case (a),static_cast<Base1mfp>(x)可以工作,但static_cast<Base2mfp>(x) 将会失败。然而case (b)的情况恰好相反。你只可以安全地将成员函数指针从派生类转换到第一个基类!你可以试一下,MSVC会抛出C4407的警告信息,而Digital Mars C++会引发一个错误。另外,两个编译器都不允许你使用reinterpret_cast来代替static_cast(为什么不允许这样做的原因各有不同)。然而,一些编译器无论你怎么做都不会提出异议。小心哪!
标准中还有另外一个有趣的法则:你可以在一个类被定义之前声明它的一个成员函数指针。这给一些编译器会带来一些预料不到的影响(我们后面再讨论)。如果可以的话,请你尽量避免这样做。
值得注意的是,和成员函数指针一样,C++标准也提供了成员数据指针。他们使用相同的操作符,一些实现问题也是一样的。他们在stl::stable_sort的实现中被用到了,但除此之外,我不知道他们还有其他什么有价值的用途。

成员函数指针的使用
到目前为止,我大概已经让你确信成员函数指针是一种有点奇异的东西。但他们到底有什么用呢?我在网上进行了大量的搜索,从网上发布的代码中大致发现成员函数指针的两种主要的使用方式:
a. 做作的例子,用于给C++的初学者演示C++的语法
b. 实现委托(Delegate)
当然还有一些微不足道的应用,如STL中的单行函数改编器和boost库(允许你使用成员函数来使用标准的算法)。在这些情况下,他们是在编译时被使用的;通常,函数指针不会出现在编译生成的代码中。成员函数指针最有趣的应用莫过于定义复杂的接口。一些重要的事情可以通过这种方式来实现,但我没有找到很多这样的例子。大多数时候,这些工作可以通过更为优雅的虚函数来完成,或者进行问题的重构。但到目前为止,成员函数指针最有名的应用是在各种应用程序的框架中。他们组成了MFC消息系统的核心。
当你使用MFC的消息映射宏(比如ON_COMMAND)的时候,你实际上提供了一个包含消息ID和成员函数指针的数组(指定为CCmdTarget::*成员函数指针)。这就是为什么MFC类如果想要处理消息的话必须从CCmdTarget类派生的原因。但是各种消息处理函数有不同的参数列表(例如OnDraw函数将CDC *作为它的第一个参数),因此那个数组必须包容各种类型的成员函数指针。MFC是如何来处理的呢?他们使用一个可怕的租借(hack),将所有可能的成员函数指针放到一个
巨大的联合体中,以此来搅乱C++通常的类型检查。(可以到afximpl.h和cmdtarg.cpp中查看MessageMapFunctions联合体,“血淋淋”啊! )因为MFC是如此重要的一块代码,实际上,所有的C++编译器支持这个租借。
在我搜索的过程中,我找不到很多关于成员函数指针的除了编译时的应用外的出色的应用例子。因为他们的复杂性,他们对语言的增值并不大。它最终难逃这样的一个结论:C++的成员函数指针的设计是存在缺陷的。
在写这篇文章的时候,我有一个主要的观点:C++标准允许你在成员函数指针之间进行转换,但转换成功后不允许你调用他们,多么可笑!可笑在下面三个原因。第一,转换在很多流行的编译器上不总是能够工作(也就是说,转换是标准的,但不是可移植的)。第二,在所有的编译器上,如果转换是成功的,调用转换后的成员函数指针的结果将符合你所期望的:(标准中)没有必要将其归类为“无定义行为”。(祈祷?是可移植的,但不是标准的!)第三,允许转化但不允许祈祷(invocation)是一无用处的。如果转换和祈祷都是可能的,那么有效的委托是很容易实现的。这将给语言带来巨大的价值。



成员函数指针——他们为什么这么复杂?

成员函数指针的实现

微软的“类最小化”方法的肮脏流言

委托

动机:最快委托的需求

诀窍:将任何成员函数指针转换成一种标准的格式

静态函数作为委托的目标

多目标委托以及其他的扩展


代码的使用


结论


参考
· [GoF] "Design Patterns: Elements of Reusable Object-Oriented Software", E. Gamma, R. Helm, R. Johnson, and J. Vlissides.
· [Boost]. Delegates can be implemented with a combination of boost::function and boost::bind. Boost::signals is one of the most sophisticated event/messaging system available. Most of the boost libraries require a highly standards-conforming compiler.
· [Loki]. Loki provides 'functors' which are delegates with bindable parameters. They are very similar to boost::function. It's likely that Loki will eventually merge with boost.
· [Qt]. The Qt library includes a Signal/Slot mechanism (i.e., delegates). For this to work, you have to run a special preprocessor on your code before compiling. Performance is very poor, but it works on compilers with very poor template support.
· [Libsigc++]. An event system based on Qt's. It avoids the Qt's special preprocessor, but requires that every target be derived from a base object class (using virtual inheritance - yuck!).
· [Hickey]. An old (1994) delegate implementation that avoids memory allocations. Assumes that all pointer-to-member functions are the same size, so itdo
esn't work on MSVC. There's a helpful discussion of the code here.
· [Haendal]. A website dedicated to function pointers?! Not much detail about member function pointers though.
· [Meyers]. Scott Meyer's article on overloading operator ->*. Note that the classic smart pointer implementations (Loki and boost)do
n't bother.
· [Sutter1]. Generalized function pointers: a discussion of how boost::function has been accepted into the new C++ standard.
· [Sutter2]. Generalizing the Observer pattern (essentially, multicast delegates) using std::tr1::function. Discusses the limitations of the failure of boost::function to provide operator ==.
· [Sutter3]. Herb Sutter's Guru of the Week article on generic callbacks.
 
-----------------------------------------------------------
在我搜索的过程中,我找不到很多关于成员函数指针的除了编译时的应用外的出色的应用例子。因为他们的复杂性,他们对语言的增值并不大。它最终难逃这样的一个结论:C++的成员函数指针的设计是存在缺陷的。
在写这篇文章的时候,我有一个主要的观点:C++标准允许你在成员函数指针之间进行转换,但转换成功后不允许你调用他们,多么可笑!可笑在下面三个原因。第一,转换在很多流行的编译器上不总是能够工作(也就是说,转换是标准的,但不是可移植的)。第二,在所有的编译器上,如果转换是成功的,调用转换后的成员函数指针的结果将符合你所期望的:(标准中)没有必要将其归类为“无定义行为”。(祈祷?是可移植的,但不是标准的!)第三,允许转化但不允许祈祷(invocation)是一无用处的。如果转换和祈祷都是可能的,那么有效的委托是很容易实现的。这将给语言带来巨大的价值。
-----------------------------------------------------------


成员函数指针和最快的C++委托
陆其明 译
原文:http://www.codeproject.com/cpp/FastDelegate.asp
作者:Don Clugston

介绍
标准C++并没有真正的面向对象的函数指针。这是很遗憾的,因为面向对象的函数指针(有时也称为委托)已经在其他语言中被证明了它的价值。在Delphi(面向对象的Pascal语言)中,面向对象的函数指针是Borland公司构建VCL(可视化组件库)的基础。最近,C#为了显示其语言本身的成功,也在力推委托(delegate)的概念。对于很多应用程序来说,委托简化了一些使用松耦合对象构建的设计模式的使用(如观察者模式Observer、策略模式Strategy、状态模式State,注:这些模式出自四巨头写的《设计模式:可复用面向对象软件的基础》一书)。毫无疑问,面向对象的函数指针对于标准C++也是很有用的。
C++没有委托的概念,它只提供成员函数指针。大部分C++程序员从来没有使用过成员函数指针,而且他们有很好的理由。因为成员函数指针有很奇异的语法(如->* ,.*),一般程序员很难确切理解他们的意思,况且大部分事情可以通过其他方式来实现。这里有一点误解:事实上,对于编译器来说,实现一个适当的委托比实现成员函数指针要容易得多。
本文,我将为大家揭开成员函数指针的神秘面纱。首先我会介绍成员函数指针的语法和特性,然后我再解释成员函数指针是如何在一般的编译器里被实现的,还有编译器如何高效地实现委托。最后,我将展示我是如何使用这些关于成员函数指针的鲜为人知的知识,来实现在大部分C++编译器上能有很高效率的委托。打个比方说,在Visual C++(6.0或.NET或.NET 2003)调用一个单目标的委托,将仅仅需要产生两行汇编代码!

函数指针
我们先来看一下函数指针。在C中,乃至后来的C++中,一个指向带一个int参数和一个char *参数、返回一个float值的函数指针(暂定为my_func_ptr)可能被声明如下:
float (*my_func_ptr)(int, char *);
// To make it more understandable, I strongly recommend that you use a typedef.
// Things can get particularly confusing when
// the function pointer is a parameter to a function.
// The declaration would then
look like this:
typedef float (*MyFuncPtrType)(int, char *);
MyFuncPtrType my_func_ptr;
注意,不同的参数组合的函数指针,类型是不一样的。在微软的VC中,不同的函数调用协议(calling conventions)也会导致函数指针类型的不同。这些函数调用协议包括__cdecl、__stdcall和 __fastcall。你可以如下使用函数指针指向一个函数float some_func(int, char *):
my_func_ptr = some_func;
当你以后想调用这个函数时,可以这么做:
(*my_func_ptr)(7, "Arbitrary String");
函数指针的类型之间是可以相互转换的。但将函数指针转型为void *却是不允许的。其它的操作这里就不作介绍了。一个函数指针可以赋值为0,来表示这是一个空指针。函数指针还可以进行一系列比较操作(可以使用比较符(==, !=, <, >, <=, >=))。你可以通过==0或隐式转型为bool来判断一个函数指针是否为空指针。有趣的是,函数指针可以被当作无类型模板参数来使用。这跟类型参数有着根本的区别,跟整体的无类型参数也是不同的。它根据名字来被实例化,而不是根据类型或值。基于名字的模板参数不被任何编译器所支持,甚至也不被所有其他的支持部分模板专用的模板所支持。
在C中,函数指针最通常地被用作一些库函数(如qsort)的参数,Windows函数的回调等。当然,函数指针还有很多其它的应用。函数指针的实现很简单:他们就是代码的指针,他们保存了汇编语言函数的起始地址。不同的函数指针类型的存在,仅仅是为了保证函数在被调用时使用正确的调用协议。

成员函数指针
在C++程序中,大多数函数都是成员函数。也就是说,他们是一个类的一部分。使用一个普通的函数指针指向一个成员函数是不允许的。正确的做法是,你必须使用一个成员函数指针。一个指向SomeClass类的一个成员函数的成员函数指针可以被声明如下(函数参数跟前面一样):
float (SomeClass::*my_memfunc_ptr)(int, char *);
// For const member functions, it's declared like this:
float (SomeClass::*my_const_memfunc_ptr)(int, char *) const;
注意到我们在上面的声明中用到了(::*) ,也就是SomeClass成为了声明的一部分。成员函数指针有一个讨厌的限制:他们只能用来指向一个类内的成员函数。不同的参数组合要使用不同类型的成员函数指针。在MSVC中,不同的函数调用协议(包括__cdecl、__stdcall、__fastcall和__thiscall,__thiscall是默认值。有趣的是,__thiscall关键字没有文档说明。有时如果你显式地使用它,会得到一个错误信息,指示这个关键字是为将来使用而保留的)也要使用不同的函数指针类型。如果你使用成员函数指针,建议你使用typedef来避免不必要的混淆。
可以如下指向函数float SomeClass::some_member_func(int, char *) :
my_memfunc_ptr = &amp;SomeClass::some_member_func;
大部分编译器(比如MSVC)允许你省略&amp;,但有一些(比如GNU G++)不允许省略。因此如果你想你写的代码具有良好的移植性,请保留&amp;。调用一个成员函数指针,你需要提供SomeClass的实例,而且你必须使用特殊的操作符->*。这个操作符的优先级很低,因此要用括号括起来:
SomeClass *x = new SomeClass;
(x->*my_memfunc_ptr)(6, "Another Arbitrary Parameter");
// You can also use the .* operator if your class is on the stack.
SomeClass y;
(y.*my_memfunc_ptr)(15, "Different parameters this time");
不要因为这种语法而指责我,看起来,c++的设计者很热衷于标点符号。
为了支持成员函数指针,c++比c语言增加了3个特殊的操作符。::*被用作声明指针,->* 和.* 被用作调用指针所指向的函数。看起来,这个语言中晦涩的、很少被使用的特性已经得到了非常的关注。(你甚至可以重载->*操作符,尽管你为什么这么做的原因已经超过了我的想象。我只是知道有这种用法而已。)
一个成员函数指针可以被设为0,可以提供==和!=操作,但仅限在同一个类的成员函数指针之间。任何成员函数指针都可以跟0比较,以此来判断是不是空指针。与一般的函数指针不同,不等比较符(<, >, <=, >=)不能用在成员函数指针之间。跟函数指针一样,成员函数指针可以被用作无类型模板参数(编译器可能要打补丁)。

诡异的成员函数指针
成员函数指针还有一些诡异的地方。首先,你不能用一个成员函数指针指向一个静态(static)的成员函数,而必须使用一个一般的函数指针。(因此,“成员函数指针”这个名字有点误导:事实上他们只是“非静态成员函数指针”。)其次,当处理派生类的时候,会有几处让你吃惊的地方。例如,下面的代码(不要改动注释部分)将在MSVC上编译通过:
class SomeClass {
public:
virtual void some_member_func(int x, char *p) {
printf("In SomeClass");
};
};
class DerivedClass : public SomeClass {
public:
// If you uncomment the next line, the code at line (*) will fail!
// virtual void some_member_func(int x, char *p) { printf("In DerivedClass");
};
};
int main() {
// Declare a member function pointer for SomeClass
typedef void (SomeClass::*SomeClassMFP)(int, char *);
SomeClassMFP my_memfunc_ptr;
my_memfunc_ptr = &amp;DerivedClass::some_member_func;
// ---- line (*)
}
很奇怪的是,&amp;DerivedClass::some_member_func是SomeClass类的一个成员函数指针,但它不是DerivedClass类的成员。(一些编译器的行为有些细小的差别:比如对于Digital Mars C++来说,这种情况下的&amp;DerivedClass::some_member_func是无定义的。)但是,如果DerivedClass类重新实现虚函数some_member_func,上面的代码将无法编译通过,因为&amp;DerivedClass::some_member_func现在变成了一个DerivedClass类的成员函数指针。
成员函数指针之间的转换是一个异常黑暗的区域。在C++标准化进程中,关于是否能够将一个成员函数指针从一个类转化到他的基类或者派生类的一个成员函数指针,以及是否能在两个不相关的类之间实现转换,存在着很多的争论。然而到标准委员会做出决定的时候,一些编译器开发商早已经有了他们自己的实现,他们对这些问题给出了自己的答案。根据标准5.2.10/9部分,你可以使用reinterpret_cast来帮助在一个类的一个成员函数指针中存储另外一个不相关类的一个成员函数。执行这种转化后的成员函数的结果是无法预料的。你唯一能做的,就只有将成员函数指针仍然转换成它原来的类型(原来是哪个类的还是转化回哪个类)。我会在下文继续讨论这个问题,因为这是个标准与实际的编译器相去甚远的部分。
在一些编译器中,诡异的事情甚至会在基类和派生类的成员函数指针之间转换时发生。当你使用了多重继承,使用reinterpret_cast将成员函数指针从一个派生类转换到一个基类可能不能被编译通过,这取决于你的派生类在声明时安排基类的顺序!这里有个例子:
class Derived: public Base1, public Base2 // case (a)
class Derived2: public Base2, public Base1 // case (b)
typedef void (Derived::* Derived_mfp)();
typedef void (Derived2::* Derived2_mfp)();
typedef void (Base1::* Base1mfp) ();
typedef void (Base2::* Base2mfp) ();
Derived_mfp x;
对于case (a),static_cast<Base1mfp>(x)可以工作,但static_cast<Base2mfp>(x) 将会失败。然而case (b)的情况恰好相反。你只可以安全地将成员函数指针从派生类转换到第一个基类!你可以试一下,MSVC会抛出C4407的警告信息,而Digital Mars C++会引发一个错误。另外,两个编译器都不允许你使用reinterpret_cast来代替static_cast(为什么不允许这样做的原因各有不同)。然而,一些编译器无论你怎么做都不会提出异议。小心哪!
标准中还有另外一个有趣的法则:你可以在一个类被定义之前声明它的一个成员函数指针。这给一些编译器会带来一些预料不到的影响(我们后面再讨论)。如果可以的话,请你尽量避免这样做。
值得注意的是,和成员函数指针一样,C++标准也提供了成员数据指针。他们使用相同的操作符,一些实现问题也是一样的。他们在stl::stable_sort的实现中被用到了,但除此之外,我不知道他们还有其他什么有价值的用途。

成员函数指针的使用
到目前为止,我大概已经让你确信成员函数指针是一种有点奇异的东西。但他们到底有什么用呢?我在网上进行了大量的搜索,从网上发布的代码中大致发现成员函数指针的两种主要的使用方式:
a. 做作的例子,用于给C++的初学者演示C++的语法
b. 实现委托(Delegate)
当然还有一些微不足道的应用,如STL中的单行函数改编器和boost库(允许你使用成员函数来使用标准的算法)。在这些情况下,他们是在编译时被使用的;通常,函数指针不会出现在编译生成的代码中。成员函数指针最有趣的应用莫过于定义复杂的接口。一些重要的事情可以通过这种方式来实现,但我没有找到很多这样的例子。大多数时候,这些工作可以通过更为优雅的虚函数来完成,或者进行问题的重构。但到目前为止,成员函数指针最有名的应用是在各种应用程序的框架中。他们组成了MFC消息系统的核心。
当你使用MFC的消息映射宏(比如ON_COMMAND)的时候,你实际上提供了一个包含消息ID和成员函数指针的数组(指定为CCmdTarget::*成员函数指针)。这就是为什么MFC类如果想要处理消息的话必须从CCmdTarget类派生的原因。但是各种消息处理函数有不同的参数列表(例如OnDraw函数将CDC *作为它的第一个参数),因此那个数组必须包容各种类型的成员函数指针。MFC是如何来处理的呢?他们使用一个可怕的租借(hack),将所有可能的成员函数指针放到一个
巨大的联合体中,以此来搅乱C++通常的类型检查。(可以到afximpl.h和cmdtarg.cpp中查看MessageMapFunctions联合体,“血淋淋”啊! )因为MFC是如此重要的一块代码,实际上,所有的C++编译器支持这个租借。
在我搜索的过程中,我找不到很多关于成员函数指针的除了编译时的应用外的出色的应用例子。因为他们的复杂性,他们对语言的增值并不大。它最终难逃这样的一个结论:C++的成员函数指针的设计是存在缺陷的。
在写这篇文章的时候,我有一个主要的观点:C++标准允许你在成员函数指针之间进行转换,但转换成功后不允许你调用他们,多么可笑!可笑在下面三个原因。第一,转换在很多流行的编译器上不总是能够工作(也就是说,转换是标准的,但不是可移植的)。第二,在所有的编译器上,如果转换是成功的,调用转换后的成员函数指针的结果将符合你所期望的:(标准中)没有必要将其归类为“无定义行为”。(祈祷?是可移植的,但不是标准的!)第三,允许转化但不允许祈祷(invocation)是一无用处的。如果转换和祈祷都是可能的,那么有效的委托是很容易实现的。这将给语言带来巨大的价值。



成员函数指针——他们为什么这么复杂?

成员函数指针的实现

微软的“类最小化”方法的肮脏流言

委托

动机:最快委托的需求

诀窍:将任何成员函数指针转换成一种标准的格式

静态函数作为委托的目标

多目标委托以及其他的扩展


代码的使用


结论


参考
· [GoF] "Design Patterns: Elements of Reusable Object-Oriented Software", E. Gamma, R. Helm, R. Johnson, and J. Vlissides.
· [Boost]. Delegates can be implemented with a combination of boost::function and boost::bind. Boost::signals is one of the most sophisticated event/messaging system available. Most of the boost libraries require a highly standards-conforming compiler.
· [Loki]. Loki provides 'functors' which are delegates with bindable parameters. They are very similar to boost::function. It's likely that Loki will eventually merge with boost.
· [Qt]. The Qt library includes a Signal/Slot mechanism (i.e., delegates). For this to work, you have to run a special preprocessor on your code before compiling. Performance is very poor, but it works on compilers with very poor template support.
· [Libsigc++]. An event system based on Qt's. It avoids the Qt's special preprocessor, but requires that every target be derived from a base object class (using virtual inheritance - yuck!).
· [Hickey]. An old (1994) delegate implementation that avoids memory allocations. Assumes that all pointer-to-member functions are the same size, so itdo
esn't work on MSVC. There's a helpful discussion of the code here.
· [Haendal]. A website dedicated to function pointers?! Not much detail about member function pointers though.
· [Meyers]. Scott Meyer's article on overloading operator ->*. Note that the classic smart pointer implementations (Loki and boost)do
n't bother.
· [Sutter1]. Generalized function pointers: a discussion of how boost::function has been accepted into the new C++ standard.
· [Sutter2]. Generalizing the Observer pattern (essentially, multicast delegates) using std::tr1::function. Discusses the limitations of the failure of boost::function to provide operator ==.
· [Sutter3]. Herb Sutter's Guru of the Week article on generic callbacks.
 
那些在Delphi中实现的面向对象的特性,
例如 as is 操作
委托函数, 属性
在C++中实际操作起来都是那么困难
写出来的句子也晦涩难懂,
而且,在语言底层的实现上,
Delphi的方法却非常简洁和简单,
不得不佩服Borland的那些天才们
 
学习,收藏...
 
看不太懂!但我还是会坚持看的,
 
lich 您好, 有注意到您很久了, 我们总经理希望能跟您认识一下, 希望能得到您的联络方式. 我们公司http://www.cerc.cn, 邮件: 2477@cerc.cn, 谢谢!
 
我的邮箱: mastercn@163.com
可以给我发邮件, 谢谢
 
接受答案了.
 
后退
顶部