C++指针探讨 (三) 成员函数指针

来源:百度文库 编辑:神马文学网 时间:2024/04/29 13:16:45

    C语言的指针相当的灵活方便,但也相当容易出错。许多C语言初学者,甚至C语言老鸟都很容易栽倒在C语言的指针下。但不可否认的是,指针在C语言中的位置极其重要,也许可以偏激一点的来说:没有指针的C程序不是真正的C程序。

    然而C++的指针却常常给我一种束手束脚的感觉。C++比C语言有更严格的静态类型,更加强调类型安全,强调编译时检查。因此,对于C语言中最容易错用的指针,更是不能放过:C++的指针被分成数据指针,数据成员指针,函数指针,成员函数指针,而且不能随便相互转换。而且这些指针的声明格式都不一样:

    还有一个更重要的区别是,指针所占的空间也不一样了。即使在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
#include
#include
#include
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 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(&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的参数达到无限个……

    ————————

    BTW: C++Builder扩展了一个关键字 closure ,允许成员函数指针如同普通函数指针一样使用。也许C++0x能考虑一下……