C++中的函数指针模板
所谓函数指针模板,就是指向函数模板的函数指针,也可以称为泛型函数指针。
问题描述:定义了一类函数模板,而且这类函数模板有共同的接口,即一致的参数列表。那么如何定义一个函数指针,使这个函数指针可以指向这一类中的所有函数模板呢?
一、先我们应当明确一点,在C++中,模板函数仅仅是一个用来生成函数的代码块,它本身是没有实体的,也就没有与“未被实例化的那些代码”相对应的程序代码块,所以也就无法对其取地址(不存在的东西,怎么会有具体的内存地址呢?)。只有在用具体类型代替模板参数,对该模板进行实例化以后才能有函数实体。
而函数指针要指向函数的入口地址,那么既然函数模板没有具体的内存地址,那么指向函数模板的函数指针如何得到地址呢?所以所谓的“函数模板指针”这个定义是无法通过以下的方法实现的:
template<class T> void (*sample)(T &);
二、尽管无法通过这个定义实现所谓的“函数模板指针”,但是并不代表C++无法解决这个问题。使用模板类与模板函数一起,可以有一种对此问题的解决方案。举例如下,(VS2010编译通过):
#include <iostream> using namespace std; class CA{ public: int Sum(int a, int b) { return a + b; } }; class CB{ public: float Sum(float a, float b) { return a + b; } }; template <typename ClassType, typename ParaType> class CC{ public: /*函数指针类型模板*/ typedef ParaType (ClassType::*pClassFun)(ParaType, ParaType); /*函数指针函数模块*/ ParaType Result(ClassType* pClassType, pClassFun fun, ParaType a, ParaType b) { return (pClassType->*fun)(a, b); } }; void main(){ /*测试整型*/ CA ca; CC<CA, int> cc; int a = 3; int b = 4; cout<<"The sum of a and b is "<<cc.Result(&ca, &CA::Sum, a, b)<<endl; /*测试浮点型*/ CB cb; CC<CB, float> fcc; float fa = 3.3f; float fb = 4.6f; cout<<"The sum of fa and fb is "<<fcc.Result(&cb, &CB::Sum, fa, fb)<<endl; }
解释:
1、第23行:需要使用typedef关键字,使编译器明白pClassFun是一个函数指针类型名(不是函数指针),这个类型的函数指针都可以指向一个函数,被指向的这个函数必须满足以下条件:返回值为ParaType、参数列表为(ParaType, ParaType)、而且是一个定义在ClassType类中的成员函数。注意:需要在作用域标示符后,函数指针类型名之前,一定要加上*(dereference),表明要定义的是函数指针类型。如果不加这个操作符,这部分就变成了对一个名叫pClassFun的成员函数进行的泛化了,那么typedef关键字的存在似乎就是没有意义的了。
2、第28行:使用这个函数指针时,需要创建一个类的实例,对函数指针fun所指向的成员函数,需要由实例来完成调用。
3、第39、46行:第二个参数需要在传入时,在参数之前加上引用操作符(取地址)。一般来说函数名就是函数的入口地址(即指针),但也许是VS的规定(此处还不是很清楚),需要使用 &ClassType::MemberFunction 这样的形式,才能创建指向成员函数的指针(error C3867: “CA::Sum”: 函数调用缺少参数列表;请使用“&CA::Sum”创建指向成员的指针)。
4、我们可以看到,由于使用泛型(即模板类)无法确定函数的入口地址,所以无法直接在函数指针上使用泛型。但是经过模板类的包装,我们就可以在类的内部使用泛型函数指针。因为类成员函数存在动态绑定技术,或者说,在加载函数时,模板类中泛型的实际类型对于模板函数来说可以说是已知的。另外,在返回函数指针时,我们多加了一层包装,形式上有些类似于C#中的代理(delegate),或者可以说,这是一种适配器模式。于是,所谓的“函数指针模板”就这样实现了。
三、如果我们定义的是包含在一组工具类中的一系列工具方法,那么一般来说,这些工具方法往往都是类的静态成员函数。静态成员函数的访问与调用,是通过ClassType::StaticMemberFunction 来实现的。对照上一部分中的解释2,工具类与工具方法最主要的特点,就是不需要实例化对象就可以直接使用方法。那么这种情况下的“函数指针模板”要如何实现呢?对上一部分中的代码稍作修改即可,举例如下(VS2010编译通过):
#include <iostream> using namespace std; class CA{ public: static int Sum(int a, int b); }; int CA::Sum(int a, int b) { return a + b; } class CB{ public: static float Sum(float a, float b); }; float CB::Sum(float a, float b) { return a + b; } template <typename ParaType> class CC{ public: /*函数指针类型模板*/ typedef ParaType (*pClassFun)(ParaType, ParaType); /*函数指针函数模块*/ ParaType Result(pClassFun fun, ParaType a, ParaType b) { return (*fun)(a, b); } }; void main(){ /*测试整型*/ CC<int> cc; int a = 3; int b = 4; cout << "The sum of a and b is " << cc.Result(CA::Sum, a, b) << endl; /*测试浮点型*/ CC<float> fcc; float fa = 3.3f; float fb = 4.6f; cout << "The sum of fa and fb is " << fcc.Result(CB::Sum, fa, fb) << endl; }
解释:
1、由于静态成员函数定义在类的外边,从易于理解的角度来说,我们可以把它们当作全局函数来调用。所以第25行的typedef类型声明和第30行函数指针的调用,都比上面的例子简单了很多。这里没有了复杂的作用域控制符等等限制,更接近于常见的普通函数指针,只不过在使用泛型时不能直接进行声明,依然是必须嵌套在模板类内部,使用模板类传来已确定的泛型类型来实现“函数指针模板”。
2、第39、45行在获取类的静态成员函数的入口地址时,只需要使用 ClassType::StaticMemberFunction 的形式即可获得函数的入口地址,完全符合”函数名即函数入口地址“的准则。在这里再看一下,上例中获取非静态成员函数入口地址的方式 &ClassType::MemberFunction,即一定要加上引用操作符才行。
四、现在我们借助C++ 11的新特性,即auto关键字和decltype关键字,就可以直接实现“函数指针模板”这个形式了。实现需要混合使用auto和decltype关键字,这也是引入decltype的一个原因——让C++有能力写一个 “ 转发函数模板”(出处:http://coolshell.cn/articles/5265.html)。
template< typename LHS, typename RHS> auto AddingFunc(const LHS &lhs, const RHS &rhs) -> decltype(lhs+rhs) {return lhs + rhs;}
这个函数模板看起来相当费解,用到了auto 和 decltype 来扩展了已有的模板技术的不足,在上例中,我不知道AddingFunc会接收什么样类型的对象,这两个对象的+操作符返回的类型也不知道,所以使用老的模板函数无法定义AddingFunc返回值和这两个对象相加后的返回值匹配,所以,你可以使用上述的这种定义。
参考:
1、http://bbs.csdn.net/topics/310122062
2、http://www.cnblogs.com/xianyunhe/archive/2011/11/27/2265148.html
3、http://www.cnblogs.com/xianyunhe/archive/2011/11/26/2264709.html
4、http://blog.csdn.net/eclipser1987/article/details/5666220