菜鸟笔记
提升您的技术认知

c 成员函数指针-ag真人游戏

  c语言的指针相当的灵活方便,但也相当容易出错。许多c语言初学者,甚至c语言老鸟都很容易栽倒在c语言的指针下。但不可否认的是,指针在c语言中的位置极其重要,也许可以偏激一点的来说:没有指针的c程序不是真正的c程序。
  然而c 的指针却常常给我一种束手束脚的感觉。c 比c语言有更严格的静态类型,更加强调类型安全,强调编译时检查。因此,对于c语言中最容易错用的指针,更是不能放过:c 的指针被分成数据指针,数据成员指针,函数指针,成员函数指针,而且不能随便相互转换。而且这些指针的声明格式都不一样:

数据指针 t *
成员数据指针 t::*
函数指针 r (*)(...)
成员函数指针 r (t::*)(...)

还有一个更重要的区别是,指针所占的空间也不一样了。即使在32位系统中,所占的空间也有可能是4字节、8字节、12字节甚至16字节,这个依据平台及编译器,有很大的变化。

  尽管c 中仍然有万能指针void*,但它却属于被批斗的对象,而且再也不能“万能”了。它不能转换成成员指针。

  这样一来,c 的指针就变得很尴尬:我们需要一种指针能够指向同一类型的数据,不管这个数据是普通数据,还是成员数据;我们更需要一种指针能够指向同一类型的函数,不管这个函数是静态函数,还是成员函数。但是没有,至少从现在的c 标准中,还没有看到。
 

  自从有了类,我们开始按照 数据+操作 的方式来组织数据结构;自从有了模板,我们又开始把 数据 和 算法 分离,以便重用,实在够折腾人的。但不管怎么折腾,现在大多数函数都不再单身,都嫁给了类,进了围城。可是我们仍然需要能够自由调用这些成员函数。
  考虑一下windows下的定时调用。settimer函数的原型是这样的:


uint_ptr settimer(
    hwnd hwnd,
    uint_ptr nidevent,
    uint uelapse,
    timerproc lptimerfunc
);
其中,参数就不解释了,这个函数估计大多数windows开发人员都知道。lptimerfunc是个会被定时调用的函数指针。假如我们不通过wm_timer消息来触发定时器,而是通过lptimerfunc来定时工作,那么我们就只能使用普通函数或静态函数,而无论如何都不能使用成员函数,哪怕通过静态函数转调也不行。

  再考虑一下线程的创建:



uintptr_t _beginthread( 
   

void

*
start_address )( 
void
 
*
 ),
   unsigned stack_size,
   

void
 
*
arglist 
);
start_address仍然只支持普通函数。不过这回好了,它允许回调函数一个void*参数,它将会arglist作为参数来调用start_address。于是,聪明的c 程序员,就利用arglist传递this指针,从而利用静态函数成功的调用到了成员函数了:



class
 mythread


{
  
public:
    
static void doit(void* pthis)
    
{
    ((mythread*)pthis)
->doit();
    }

    
void doit(){}
}

;

main()


{
  
  mythread
* pmt = new mythread;
  _beginthread(
&mythread::doit, 0, (void*)pmt);
  
}

  但是显然,c 程序员肯定不会因此而满足。这里头有许多被c 批判的不安定因素。它使用了c 中被认为不安全的类型转换,不安全的void*指针,等等等等。但这是系统为c语言留下的调用接口,这也就认了。那么假如,我们就在c 程序中如何来调用成员函数指针呢?
  如下例,我们打算对vector中的所有类调用其指定的成员函数:


#include 
<
vector
>

#include 

<
algorithm
>

#include 

<
functional
>

#include 

<
iostream
>


using
 
namespace
 std;


class
 a


{
    
int value;
public:
    a(
int v){value = v;}
    
void doit(){ cout << value << endl;};
    
static void call_doit(a& rthis)
    
{
        rthis.doit();
    }

}

;



int
 main()


{
    vector
<a> va;
    va.push_back(a(
1));
    va.push_back(a(
2));
    va.push_back(a(
3));
    va.push_back(a(
4));
    
//方法1:
    
//for_each(va.begin(), va.end(), &a::doit); //error
    
//方法2:
    for_each(va.begin(), va.end(), &a::call_doit);
    
//方法3:
    for_each(va.begin(), va.end(), mem_fun_ref<void, a>(&a::doit));

    system(
"pause");

    
return 0;
}


  方法1,编译不能通过。for_each只允许具有一个参数的函数指针或函数对象,哪怕a::doit默认有一个this指针参数也不行。不是for_each没考虑到这一点,而是根本做不到!
  方法2,显然是受到了beginthread的启发,使用一个静态函数来转调用,哈哈成功了。但是不爽!这不是c 。
  方法3,呼,好不容易啊,终于用mem_fun_ref包装成功了成员函数指针。
  似乎方法3不错,又是类型安全的,又可以通用--慢着,首先,它很丑,哪有调用普通c函数指针那么漂亮啊(见方法2),用了一大串包装,又是尖括号又是圆括号,还少不了&号!其次,它只能包装不超过一个参数的函数!尽管它在for_each中够用了,但是你要是想用在超过一个参数的场合,那只有一句话:不可能的任务。

  是的,在标准c 中,这是不可能的任务。但事情并不总是悲观的,至少有许多第三方库提供了超越mem_fun的包装。如boost::function等等。但是它也有限制:它所支持的参数仍然是有限的,只有十多个,尽管够你用的了;同样,它也是丑陋的,永远不要想它能够简单的用&来搞定。

  也许,以失去美丽的代价,来换取质量上的保证,这也是c 对于函数指针的一种无奈吧……

  期待c 0x版本。它通过可变模板参数,能够让mem_fun的参数达到无限个……

网站地图