通用型函数指针

作者: | 更新日期:

看了 kevinlynx 的一篇文章,然后按自己的理解重新实现一个通用型函数指针。

本文首发于公众号:天空的代码世界,微信号:tiankonguse

看了 kevinlynx 的一篇通用型函数指针的文章,发现使用到的技术知识自己都知道,于是想着自己也实现一个来练练手。

什么是通用型的函数指针呢?

这个不好解释,不过可以用例子来让大家看明白。

对于平常的指针,比如整形指针,我们直接可以像下面的形式操作

  1. void normal() {
  2. int one = 1;
  3. int* pOne;
  4. pOne = &one;
  5. printf("pOne %d\n", *pOne);
  6. int two = 2;
  7. int* pTwo= &two;
  8. printf("pTwo %d\n", *pTwo);
  9. int three = 3;
  10. int* pThree(&three);
  11. printf("pThree %d\n", *pThree);
  12. printf("end normal\n\n");
  13. }

这里我们可以看到整形指针有这么几个性质。

  1. 普通指针可以在定义时初始化
  2. 普通指针可以在正常赋值
  3. 我们可以操作指针的值

函数指针是什么样子呢?

  1. void testPointFun(int num) {
  2. printf("testPointFun %d\n",num);
  3. }
  4. void testPointFunTwo(int num, int num2) {
  5. printf("testPointFunTwo %d %d\n",num, num2);
  6. }
  7. void pointFun() {
  8. void (*pFunOne)(int);
  9. pFunOne = testPointFun;
  10. pFunOne(1);
  11. void (*pFunTwo)(int) = testPointFun;
  12. pFunTwo(2);
  13. void (*pFunThree)(int)(testPointFun);
  14. pFunThree(3);
  15. typedef void (*PestPointFun)(int);
  16. PestPointFun pFunFour = testPointFun;
  17. pFunFour(4);
  18. typedef void (*PestPointFunTwo)(int, int);
  19. PestPointFunTwo pFunFive = testPointFunTwo;
  20. pFunFive(5,5);
  21. printf("end pointFun\n\n");
  22. }

我们发现,普通指针也都可以做这些操作,但是我们需要使用函数指针那么很长很长的定义,即使使用 typedef , 也要为每一种函数声明单独定义新类型的名字。

于是我们想,能不能直接定义函数指针呢?

比如这样

  1. void wantPointFun() {
  2. PointFun pointFunOne = testPointFun;
  3. pointFunOne(6);
  4. PointFun pointFunTwo = testPointFunTwo;
  5. pointFunTwo(7,7);
  6. printf("end wantPointFun\n\n");
  7. }

当然,根据一个函数名自动推导出对应的函数指针的技术可以实现,但是cpp标准中又没有这样的技术我就不知道了。

我们就假设cpp中现在没有这样的技术吧。

既然目前标准中不支持这种技术,那我们该如何实现呢?

于是只好自己指定好类型了。

例如这样

  1. template <typename _R, typename _P1>
  2. class functor {
  3. public:
  4. typedef _R (*func_type)( _P1 );
  5. public:
  6. explicit functor( const func_type &func ) :
  7. _func( func ) {
  8. }
  9. _R operator() ( _P1 p ) {
  10. return _func( p );
  11. }
  12. private:
  13. func_type _func;
  14. };
  15. int testPointFun(int num) {
  16. printf("testPointFun %d\n",num);
  17. return 0;
  18. }
  19. void firstPointFun() {
  20. functor<int, int> cmd( testPointFun );
  21. cmd( 1 );
  22. }

于是我们通过重载类的运算符 () 来模拟函数调用就完美的解决问题了。

但是我们既然可以使用类来模拟函数(姑且称为函数对象吧), 那传过来的函数指针会不会就是我们的那个函数对象呢?

  1. struct Func {
  2. int operator() ( int i ) {
  3. return i;
  4. }
  5. };
  6. void secondPointFun() {
  7. functor<int, int> cmd1( testPointFun );
  8. cmd1(1);
  9. Func obj;
  10. functor<int, int> cmd2(obj);
  11. cmd2( 2 );
  12. }

我们发现对于函数对象, 编译不通过。提示这个错误

  1. error: no matching function for call to 'functor<int, int>::functor(Func&)'

报这个错误也正常,我们的通用函数指针式 int (*)(int) 类型, 但是我们传进去的是 Func 类型,当然不匹配了。

这个时候我们就会意识到需要对这个函数的类型进行抽象了,比如这样

  1. template <typename _R, typename _P1,typename _FuncType>
  2. class functor {
  3. public:
  4. typedef _FuncType func_type;
  5. public:
  6. explicit functor( const func_type &func ) :
  7. _func( func ) {
  8. }
  9. _R operator() ( _P1 p ) {
  10. return _func( p );
  11. }
  12. private:
  13. func_type _func;
  14. };
  15. int testPointFun(int num) {
  16. printf("testPointFun %d\n",num);
  17. return 0;
  18. }
  19. struct Func {
  20. int operator() ( int num ) {
  21. printf("Func class %d\n",num);
  22. return num;
  23. }
  24. };
  25. void threePointFun() {
  26. functor<int, int, int (*)(int)> cmd1( testPointFun );
  27. cmd1(1);
  28. Func obj;
  29. functor<int, int, Func> cmd2(obj);
  30. cmd2( 2 );
  31. }

这个时候我们终于编译通过了。

但是,编译通过的代价却是我们手动指定函数指针的类型, 这与直接声明函数指针变量有什么区别呢?

比如对于上面的,我们直接使用函数指针不是更方便吗?

  1. void fourPointFun() {
  2. int (*cmd1)(int) ( testPointFun );
  3. cmd1(1);
  4. Func obj;
  5. Func cmd2(obj);
  6. cmd2( 2 );
  7. }

那我们为了什么那样这样的寻找所谓的’通用型函数指针’呢?

答案是为了统一函数指针的定义,对,是统一。

那我们能不能省去函数指针的类型呢?

貌似使用多态可以省去函数指针的类型,可以让系统自己推导,然后我们只需要调用函数即可。

例如这样

  1. template <typename _R, typename _P1>
  2. struct handler_base {
  3. virtual _R operator() ( _P1 ) = 0;
  4. };
  5. template <typename _R, typename _P1, typename _FuncType>
  6. class handler : public handler_base<_R, _P1> {
  7. public:
  8. typedef _FuncType func_type;
  9. public:
  10. handler( const func_type &func ) :
  11. _func( func ) {
  12. }
  13. _R operator() ( _P1 p ) {
  14. return _func( p );
  15. }
  16. public:
  17. func_type _func;
  18. };
  19. template <typename _R, typename _P1>
  20. class functor {
  21. public:
  22. typedef handler_base<_R, _P1> handler_type ;
  23. public:
  24. template <typename _FuncType>
  25. functor( _FuncType func ) :
  26. _handler( new handler<_R, _P1, _FuncType>( func ) ) {
  27. }
  28. ~functor() {
  29. delete _handler;
  30. }
  31. _R operator() ( _P1 p ) {
  32. return (*_handler)( p );
  33. }
  34. private:
  35. handler_type *_handler;
  36. };
  37. int testPointFun(int num) {
  38. printf("testPointFun %d\n",num);
  39. return 0;
  40. }
  41. struct Func {
  42. int operator() ( int num ) {
  43. printf("Func class %d\n",num);
  44. return num;
  45. }
  46. };
  47. void fivePointFun() {
  48. functor<int, int>cmd1( testPointFun );
  49. cmd1(1);
  50. Func obj;
  51. functor<int, int>cmd2(obj);
  52. cmd2( 2 );
  53. }

我们通过模板和多态实现了指定参数的通用型函数指针。
由于模板是编译的时候确定类型的,所以参数的个数需要编译的时候确定。
又由于模板不支持任意类型参数,所以我们只好把不同个数参数的模板都定义了。

这里有涉及到怎么优雅的定义不同个数参数的模板了。

去年我去听过一个培训,讲的是就是c++的模板,重点讲了偏特化。

我们利用偏特化就可以暂时解决这个问题。

实现代码可以参考我的 github

看了实现代码,发现使用起来还是很不方便。

  1. functor<int, TYPE_LIST1(int)>cmd1( testPointFun );
  2. cmd1(1);
  3. Func obj;
  4. functor<int, TYPE_LIST1(int)>cmd2(obj);
  5. cmd2( 2 );
  6. functor<int, TYPE_LIST2(int,int)>cmd3( testPointFunTwo );
  7. cmd3(1,2);

需要我们手动指定参数的个数,以及传进去参数的类型。

由于我们不能自动推导参数的类型,所以类型必须手动指定,但是个数我们应该可以在编译器期确定吧。

现在我们的目的是这样的使用函数指针。

  1. functor<int, TYPE_LIST(int)>cmd1( testPointFun );
  2. cmd1(1);
  3. Func obj;
  4. functor<int, TYPE_LIST(int)>cmd2(obj);
  5. cmd2( 2 );
  6. functor<int, TYPE_LIST(int,int)>cmd3( testPointFunTwo );
  7. cmd3(1,2);

这个倒是很容易实现。比如这样

  1. #define NUM_PARAMS(...) NUM_PARAMS_OUTER(__VA_ARGS__, NUM_PARAMS_EXTEND())
  2. #define NUM_PARAMS_OUTER(...) NUM_PARAMS_INTER(__VA_ARGS__)
  3. #define NUM_PARAMS_INTER( _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, _11,_12,_13,_14,_15,_16, N, ...) N
  4. #define NUM_PARAMS_EXTEND() 16,15,14,13,12,11,10, 9,8,7,6,5,4,3,2,1,0
  5. #define TYPE_LIST1( T1 ) type_list<T1, null_type>
  6. #define TYPE_LIST2( T1, T2 ) type_list<T1, TYPE_LIST1( T2 )>
  7. #define TYPE_LIST3( T1, T2, T3 ) type_list<T1, TYPE_LIST2( T2, T3 )>
  8. #define TYPE_LIST(...) TYPE_LIST_N(NUM_PARAMS(__VA_ARGS__), __VA_ARGS__)
  9. #define TYPE_LIST_N(n,...) TYPE_LIST_N_FIX(n, __VA_ARGS__)
  10. #define TYPE_LIST_N_FIX(n, ...) TYPE_LIST##n(__VA_ARGS__)

这个实现还是有一点不爽: 我们需要写出所有可能的 TYPE_LISTn.

能不能使用宏来做到这个呢?

宏中怎么才能判断出到到达最后一个参数或者没有参数了呢?

还是依靠得到宏个数的技术。

但是经过嵌套尝试,发现宏时不能递归展开的。

好吧,既然不能递归展开,那也只能到达这一步了。

源代码可以参考我的 github.

本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。

关注公众号,接收最新消息

tiankonguse +
穿越