类的成员函数指针(比較深入)

From:http://blog.csdn.net/hairetz/archive/2009/05/06/4153252.aspx

个人感觉对于类的成员函数指针这块解说的比較深入具体

推荐阅读

/////////////////////////////////////////////////

先看这样一段代码

class test
{
   public:
      test(int i){ m_i=i;}
      test(){}

void hello()
      {
           printf("hello/n");
      }
   private:
       int m_i;
};

int main()
{
     test *p=new test();
     p->hello(); 
     p=NULL; 
     p->hello();
}

结果是:

hello

hello

为何

p=NULL; 
     p->hello();   这样之后,NULL->hello()也依旧有效呢?

我们第一反应一定是认为类的实例的成员函数,不同于成员变量,成员函数所有都存在同一个地方,所以的类的实例来调用的时候,一定是调用同样的函数指针。(想想也是有道理的,成员函数都是一样的功能,根本不须要实例化多个)

于是我们把代码改成这样

class test
{
    public:
        test(int i){ m_i=i;}
        test(){}

void hello()
        {
            printf("hello/n");
        }

private:
        int m_i;
};

typedef void (test::*HELLO_FUNC)();

int main()
{
     test *p=new test();
      test q;
      p->hello();
      HELLO_FUNC phello_fun=&test::hello;
      printf("%p/n",phello_fun);
      p=NULL;
      phello_fun=&test::hello;
      printf("%p/n",phello_fun);
      phello_fun=p->hello;
      printf("%p/n",phello_fun);
      phello_fun=q.hello;
      printf("%p/n",phello_fun);
      p->hello();
}

结果是:

hello
00401005
00401005
00401005
00401005
hello
Press any key to continue

也就是说无论是&test::hello,还是p->hello,或者q.hello,甚至于NULL->hello.

调用的地址都是0x00401005,也基本印证了我们的猜想。

事情到这里算是完了么?没有。

有人问道这样一段代码:

SSVector& SSVector::assign2product4setup(const SVSet& A, const SSVector& x)
{
    int    ret_val=

pthread_create(&pt,NULL,(void(*)(void*))SSVector::prefun,x);
}

void* SSVector::prefun (void* arg){
         const SSVector &tx =*((SSVector*) arg);
}
行报错:invalid conversion from ‘void (*)(void*)‘ to ‘void* (*)(void*)‘

pthread_create我就不解释了,第3个參数是线程函数的指针,为何这里报错呢?

说明普通的类成员函数的指针(假设它有函数指针的话),不同于一般的函数指针。

看看以下这篇文章关于这个问题的分析:

前言:在CSDN论坛常常会看到一些关于类成员函数指针的问题,起初我并不在意,以为成员函数指针和普通的函数指针是一样的,没有什么太多须要讨论的。当我找来相关书籍查阅了一番以后,突然意识到我曾经对成员函数指针的理解太过于幼稚和肤浅了,它即不像我曾经觉得的那样简单,它也不像我曾经觉得的那样"默默无闻"。强烈的求知欲促使我对成员函数进行进一步的学习并有了这篇文章。

一。理论篇
在进行深入学习和分析之前,还是先看看书中是怎么介绍成员函数的。总结一下类成员函数指针的内容,应该包括下面几个知识点:
1。成员函数指针并非普通的函数指针。
2。编译器提供了几个新的操作符来支持成员函数指针操作:

1) 操作符"::*"用来声明一个类成员函数指针,比如:
    typedef void (Base::*PVVBASEMEMFUNC)(void);        //Base is a class
2) 操作符"->*"用来通过对象指针调用类成员函数指针,比如:
    //pBase is a Base pointer and well initialized
    //pVIBaseMemFunc is a member function pointer and well initialized
    (pBase->*pVIBaseMemFunc)();
3) 操作符".*"用来通过对象调用类成员函数指针,比如:
    //baseObj is a Base object
    //pVIBaseMemFunc is a member function pointer and well initialized
    (baseObj.*pVIBaseMemFunc)();

3。成员函数指针是强类型的。

typedef void (Base::*PVVBASEMEMFUNC)(void);
    typedef void (Derived::*PVVDERIVEMEMFUNC)(void);
PVVBASEMEMFUNC和PVVDERIVEMEMFUNC是两个不同类型的成员函数指针类型。

4。因为成员函数指针并非真真意义上的指针,所以成员函数指针的转化就受限制。详细的转化细节依赖于不同的编译器,甚至是同一个编译器的不同版本号。只是,处于同一个继承链中的不同类之间override的不同函数和虚函数还是能够转化的。

void* pVoid = reinterpret_cast<void*>(pVIBaseMemFunc);            //error
    int*  pInt  = reinterpret_cast<int*>(pVIBaseMemFunc);             //error
  pVIDeriveMemFunc = static_cast<PVIDERIVEMEMFUNC>(pVIBaseMemFunc);   //OK

二。实践篇
有了上面的理论知识,我们对类成员函数指针有了大概的了解,但是我们对成员函数指针还存在太多的疑惑。既然说成员函数指针不是指针,那它究竟是什么东东? 编译器为什么要限制成员函数指针转化?老办法,我们还是分析汇编代码揭示当中的秘密。首先,我写了这样两个具有继承关系的类:
接着,我又定义了一些成员函数指针类型:
最后,在main函数写了一些測试代码:
成功编译后生成汇编代码。老规矩,在分析汇编代码的过程中还是仅仅分析对解决这个问题有意义的汇编代码,其它的就临时忽略。
1。成员函数指针不是指针。从代码看出,在main函数的调用栈(calling stack)中首先依次压入四个成员函数指针,假设它们是普通指针的话,它们之间的偏移量应该是4个字节,但是实际的情况却是这种:

”The implementation of the pointer to member function must store within itself information as to whether the member function to which it refers is virtual or nonvirtual, information about where to find the appropriate virtual function table pointer (see The Compiler Puts Stuff in Classes [11, 37]), an offset to be added to or subtracted from the function‘s this pointer (see Meaning of Pointer Comparison [28, 97]), and possibly other information. A pointer to member function is commonly implemented as a small structure that contains this information, although many other implementations are also in use. Dereferencing and calling a pointer to member function usually involves examining the stored information and conditionally executing the appropriate virtual or nonvirtual function calling sequence.“

2。成员函数指针的转化。本文所採用的代码是想比較普通成员函数指针和虚函数指针在转化的过程中存在那些差异:
对于符号”[email protected]$B3AE“,我又找到了这种汇编代码: 由此能够看出,对于虚函数,即使是用过成员函数指针间接调用,仍然具有和直接调用一样的特性。

; PVIBASEMEMFUNC pVIBaseMemFunc = &Base::setValue;
    mov    DWORD PTR _pVIBaseMemFunc$[ebp], OFFSET FLAT:[email protected]@@[email protected] ;
    取出Base::setValue函数的地址,存放于变量pVIBaseMemFunc所占内存的前4个字节(DWORD)中。

; PVVBASEMEMFUNC      pVVBaseMemFunc   = &Base::foobar;
mov    DWORD PTR _pVVBaseMemFunc$[ebp], OFFSET FLAT:[email protected]$B3AE ; `vcall‘
取出符号”[email protected]$B3AE“的值,存放于变量pVVBaseMemFunc所占内存的前4个字节(DWORD)中。

_TEXT    SEGMENT
    [email protected]$B3AE PROC NEAR                    ; `vcall‘, COMDAT
    mov    eax, DWORD PTR [ecx]
    jmp    DWORD PTR [eax+4]
    [email protected]$B3AE ENDP                        ; `vcall‘
    _TEXT    ENDS
符号”[email protected]$B3AE“代表的应该是一个存根函数,这个函数首先依据this指针获得虚函数表的指针,然后将指令再跳转到对应的虚函数的地址。

; PVIDERIVEMEMFUNC pVIDeriveMemFunc = static_cast<PVIDERIVEMEMFUNC>(pVIBaseMemFunc);
    mov    eax, DWORD PTR _pVIBaseMemFunc$[ebp]
    mov    DWORD PTR _pVIDeriveMemFunc$[ebp], eax
直接将变量pVIBaseMemFunc所占内存的前4个字节(DWORD)的值付给了变量_pVIDeriveMemFunc所占内存的前4个字节中。

; PVVDERIVEMEMFUNC    pVVDeriveMemFunc = static_cast<PVVDERIVEMEMFUNC>(pVVBaseMemFunc);
    mov    eax, DWORD PTR _pVVBaseMemFunc$[ebp]
    mov    DWORD PTR _pVVDeriveMemFunc$[ebp], eax
直接将变量pVVBaseMemFunc所占内存的前4个字节(DWORD)的值付给了变量pVVDeriveMemFunc所占内存的前4个字节中。

由此能够看出,基类的成员函数指针转化到对应的派生类的成员函数指针,值保持不变。当然这里的样例继承关系相对来说比較简单,假设存在多继承和虚继承的情况下,结果可能会复杂的多。

3。函数调用
以下的函数调用都大同小异,这里是列出当中的一个: 这里的汇编代码并没有给我们太多新奇的内容:将对象的首地址(this指针)存放于寄存器ECX中,接着就将指令转到变量_pVIBaseMemFunc所占内存的前4个字节所表示的地址。

到了这里,我们应该对成员函数指针有了进一步的了解。

; (baseObj.*pVIBaseMemFunc)(10);
    mov    esi, esp
    push    10                    ; 0000000aH
    lea    ecx, DWORD PTR _baseObj$[ebp]
    call    DWORD PTR _pVIBaseMemFunc$[ebp]
    cmp    esi, esp
    call    __RTC_CheckEsp

由此能够看出,他们之间的偏移量是12个字节。这12个字节中应该能够包括三个指针,当中的一个指针应该指向函数的地址,那另外两个指针又指向那里呢?在《C++ Common Knowledge: Essential Intermediate Programming》(中文译名:

C++必知必会)这本书的第16章对这部分的内容做了说明,这个12个字节的偏移量正好印证了书中的内容:

class Base {
public:
    //ordinary member function
    void setValue(int iValue);

//virtual member function
    virtual void dumpMe();
    virtual void foobar();

protected:
    int m_iValue;
};

class Derived:public Base{
public:
    //ordinary member function
    void setValue(int iValue);

//virtual member function
    virtual void dumpMe();
    virtual void foobar();
private:
    double m_fValue;
};

typedef void (Base::*PVVBASEMEMFUNC)(void);
    typedef void (Derived::*PVVDERIVEMEMFUNC)(void);
    typedef void (Base::*PVIBASEMEMFUNC)(int);
    typedef void (Derived::*PVIDERIVEMEMFUNC)(int);

int _tmain(int argc, _TCHAR* argv[])
{
    PVIBASEMEMFUNC pVIBaseMemFunc = &Base::setValue;
    PVIDERIVEMEMFUNC pVIDeriveMemFunc = static_cast<PVIDERIVEMEMFUNC>(pVIBaseMemFunc);

PVVBASEMEMFUNC      pVVBaseMemFunc   = &Base::foobar;
    PVVDERIVEMEMFUNC    pVVDeriveMemFunc = static_cast<PVVDERIVEMEMFUNC>(pVVBaseMemFunc);

Base baseObj;
    (baseObj.*pVIBaseMemFunc)(10);
    (baseObj.*pVVBaseMemFunc)();

Derived deriveObj;
    (deriveObj.*pVIDeriveMemFunc)(20);
    (deriveObj.*pVVDeriveMemFunc)();

return 0;
}

_deriveObj$ = -88
_baseObj$ = -60
_pVVDeriveMemFunc$ = -44
_pVVBaseMemFunc$ = -32
_pVIDeriveMemFunc$ = -20
_pVIBaseMemFunc$ = -8
_argc$ = 8
_argv$ = 12

时间: 2024-10-05 22:09:59

类的成员函数指针(比較深入)的相关文章

VB6/VBA中跟踪鼠标移出窗体控件事件(类模块成员函数指针CHooker类应用)

前几天发了一篇博文,是关于获取VB类模块成员函数指针的内容(http://www.cnblogs.com/alexywt/p/5880993.html):今天我就发一下我的应用实例. VB中默认是没有鼠标移出事件响应的,而这个事件其实在项目开发中,实用性很强,很多时候需要在鼠标移出窗体或控件时做些事情:没有这个事件会感觉很费力: 今天我所说的实际案例就是,在窗体上,设计一个SplitterBar控件,窗体的最终用户使用这个控件可以在运行程序时任意调整其内部控件大小. 我在第二篇参考博文作者开发的

类的成员函数指针

一个类的成员函数指针使用前,必须添加一个类的对象. 普通类的成员函数指针建立: 返回值 (类名::指针名)(函数参数)=void (A::*pt)(int,bool); 初级实例代码一: 1 #include <iostream> 2 using namespace std; 3 4 class A 5 { 6 public: 7 void set(int x,int y){i=x,j=y;} 8 int get(){return i*j;} 9 private: 10 int i; 11 i

类的成员函数指针和mem_fun适配器的用法

先来看一个最简单的函数: void foo(int a) { cout << a << endl; } 它的函数指针类型为 void (*)(int); 我们可以这样使用: void (*pFunc)(int) = &foo; pFunc(123); 这就是函数指针的基本使用.   类的成员函数   那么,对于类的成员函数,函数指针有什么不同呢? 我们观察下面的类: class Foo { public: //void (Foo::*)(int) void foo(int

C++——类的成员函数指针以及mem_fun适配器

有这样一个类,我们以此类为基础: 1 class Foo 2 { 3 public: 4 5 //void (Foo::*)(int) 6 void foo(int a) 7 { 8 cout << a << endl; 9 } 10 11 //void (*)(int) 12 static void bar(int a) 13 { 14 cout << a << endl; 15 } 16 }; 我们尝试调用函数指针: void (*pFunc)(int)

获取VB类模块成员函数指针(转)

最近在做一些VB6.VBA的项目,被如何获取类模块中的函数指针这个问题所困扰,收集整理后,有2分资料值得收藏,特将关键部分留存,以备后续查找. 参照连接1:http://www.cnblogs.com/pctgl/articles/1352916.html 参照连接2:http://blog.csdn.net/lyserver/article/details/4224676 以下是链接1中的部分内容: 1. 函数地址 = GetClassProcAddress ( 指定为哪个函数 [上面解释],

C++学习之成员函数指针

C++中有函数指针,申明方式如下: void(*p)(float) 其中p就是一个函数指针,如果我们定义一个函数 void fun(float) 那么我们可以p = fun 或者p = &fun来给p赋值 于此同时还有一个概念叫做成员函数指针,这个指针和函数指针类似,所不同的是它是一个指向类的成员函数的指针,其声名方式如下: void (class_name::*p)(float) 这就代表p是一个指向class_name类中形如void fun_name(float)的函数的函数指针 赋值的方

一般函数指针与成员函数指针

函数指针,顾名思义,指向函数的指针. C++中函数指针的声明形式为: void (*pfn)() C++中函数指针的赋值:pfn=funName 或 &funName C++中函数指针的使用:pfn() 或(*fun)() 看到了上面的赋值跟使用的时候,我们不禁会产生疑问,为什么指针的赋值可以用函数名?又可以用取地址的形式赋值呢?为什么可以通过指针可以直接调用函数呢?指针不是需要解引用才能访问指向的内容吗?这个我表示也暂时不理解编译器编译的时候的具体赋值细节.不过这里我们可以先把函数名,当作数组

成员函数指针与高性能的C++委托

成员函数指针与高性能的C++委托(上篇) 撰文:Don Clugston 引子 标准C++中没有真正的面向对象的函数指针.这一点对C++来说是不幸的,因为面向对象的指针(也叫做"闭包(closure)"或"委托 (delegate)")在一些语言中已经证明了它宝贵的价值.在Delphi (Object Pascal)中,面向对象的函数指针是Borland可视化组建库(VCL,Visual Component Library)的基础.而在目前,C#使"委托&

类的成员函数的指针

前面一篇文章<函数的指针 >介绍了全局函数的指针,现在我们再来介绍一下成员函数的指针. 成员函数指针的定义: 一般形式 Return_Type (Class_Name::* pointer_name) (Argument_List); 用typedef简化的形式 Typedef Return_Type (Class_Name::* FuncPtr_Type) (Argument_List); FuncPtr_Type pFunc = NULL; //pFunc为成员函数指针的指针变量 成员函数