`

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

阅读更多

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
。成员函数指针的转化。本文所采用的代码是想比较普通成员函数指针和虚函数指针在转化的过程中存在那些差异: 
对于符号”??_9@$B3AE“,我又找到了这样的汇编代码: 由此可以看出,对于虚函数,即使是用过成员函数指针间接调用,仍然具有和直接调用一样的特性。

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

 

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

 

    _TEXT    SEGMENT
    ??_9@$B3AE PROC NEAR                    ; `vcall', COMDAT
    mov    eax, DWORD PTR [ecx]
    jmp    DWORD PTR [eax+4]
    ??_9@$B3AE ENDP                        ; `vcall'
    _TEXT    ENDS
符号”??_9@$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

 

分享到:
评论

相关推荐

    一般函数指针和类的成员函数指针深入解析

    由于类的非静态成员函数中有一个隐形的this指针,因此,类的成员函数的指针和一般函数的指针的表现形式不一样。 1、指向一般函数的指针函数指针的声明中就包括了函数的参数类型、顺序和返回值,只能把相匹配的函数...

    深入探讨C++的this指针

    this指针作为一个隐含参数传递给非静态成员函数,用以指向该成员函数所属类所定义的对象

    深入理解c++常成员函数和常对象

    先明确几个概念: 1. 常对象只能调用常成员函数。 2. 普通对象可以调用全部成员函数。...在X类的const成员函数中,this指针的类型为:const X* const, 这说明this指针所指向的这种对象是不可修改的(即不能对这种

    深入解析C++编程中的静态成员函数

    与数据成员类似,成员函数也可以定义为静态的,在类中声明函数的前面加static就成了静态成员函数。如 static int volume( ); 和静态数据成员一样,静态成员函数是类的一部分,而不是对象的一部分。 如果要在类外...

    c++ 虚函数与纯虚函数的区别(深入分析)

    那么,什么是虚函数呢,我们先来看看微软的解释: 虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。

    C++ Primer第四版【中文高清扫描版】.pdf

    7.7.2 在类外定义成员函数 225 7.7.3 编写Sales_item类的构造 函数 225 7.7.4 类代码文件的组织 227 7.8 重载函数 228 7.8.1 重载与作用域 230 7.8.2 函数匹配与实参转换 231 7.8.3 重载确定的三个步骤 232 7.8.4 ...

    虚函数与纯虚函数(C++与Java虚函数的区别)的深入分析

    用途:实现多态性,通过指向派生类的基类指针,访问派生类中同名覆盖成员函数,也就是允许子类override父类同名方法。虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数...

    程序员必备知识点整理包括编程、面试、刷题等

    10)析构函数不能重载11)类的常成员函数12).虚函数使用实例: 13)C++析构函数为什么要为虚函数15)数组操作中对于指针的深入理解 16)、锁的理解 17) C++11 并发与多线程 18)binder通信 19)LInux 1.12 C++ ...

    visualC++2010入门经典源代码

    7.7.2 类外部的成员函数定义 328 7.8 类对象的数组 329 7.9 类的静态成员 331 7.9.1 类的静态数据成员 331 7.9.2 类的静态函数成员 334 7.10 类对象的指针和引用 334 7.10.1 类对象的指针 334 7.10.2 类对象...

    Visual C++ 2005入门经典.part08.rar (整理并添加所有书签)

    7.7.2 类外部的成员函数定义 7.8 类对象的数组 7.9 类的静态成员 7.9.1 类的静态数据成员 7.9.2 类的静态函数成员 7.10 类对象的指针和引用 7.10.1 类对象的指针 7.10.2 类对象的引用 7.11 C++/CLI编程 7.11.1 定义...

    Visual C++ 2005入门经典.part04.rar (整理并添加所有书签)

    7.7.2 类外部的成员函数定义 7.8 类对象的数组 7.9 类的静态成员 7.9.1 类的静态数据成员 7.9.2 类的静态函数成员 7.10 类对象的指针和引用 7.10.1 类对象的指针 7.10.2 类对象的引用 7.11 C++/CLI编程 7.11.1 定义...

    [Visual.C++.2010入门经典(第5版)].Ivor.Horton.part1

    7.7.2 类外部的成员函数定义 328 7.8 类对象的数组 329 7.9 类的静态成员 331 7.9.1 类的静态数据成员 331 7.9.2 类的静态函数成员 334 7.10 类对象的指针和引用 334 7.10.1 类对象的指针 334 7.10.2 类对象的引用 ...

    Visual C++ 2005入门经典.part07.rar (整理并添加所有书签)

    7.7.2 类外部的成员函数定义 7.8 类对象的数组 7.9 类的静态成员 7.9.1 类的静态数据成员 7.9.2 类的静态函数成员 7.10 类对象的指针和引用 7.10.1 类对象的指针 7.10.2 类对象的引用 7.11 C++/CLI编程 7.11.1 定义...

    Visual C++ 2005入门经典.part09.rar (整理并添加所有书签)

    7.7.2 类外部的成员函数定义 7.8 类对象的数组 7.9 类的静态成员 7.9.1 类的静态数据成员 7.9.2 类的静态函数成员 7.10 类对象的指针和引用 7.10.1 类对象的指针 7.10.2 类对象的引用 7.11 C++/CLI编程 7.11.1 定义...

    Visual C++ 2005入门经典.part06.rar (整理并添加所有书签)

    7.7.2 类外部的成员函数定义 7.8 类对象的数组 7.9 类的静态成员 7.9.1 类的静态数据成员 7.9.2 类的静态函数成员 7.10 类对象的指针和引用 7.10.1 类对象的指针 7.10.2 类对象的引用 7.11 C++/CLI编程 7.11.1 定义...

    Visual C++ 2005入门经典.part05.rar (整理并添加所有书签)

    7.7.2 类外部的成员函数定义 7.8 类对象的数组 7.9 类的静态成员 7.9.1 类的静态数据成员 7.9.2 类的静态函数成员 7.10 类对象的指针和引用 7.10.1 类对象的指针 7.10.2 类对象的引用 7.11 C++/CLI编程 7.11.1 定义...

    零起点学通C++多媒体范例教学代码

    6.6 成员函数的声明和定义 6.7 内联函数 6.7.1 普通内联函数 6.7.2 内联成员函数 6.8 头文件与源文件 6.9 const成员函数 6.10构造函数 6.11默认构造函数 6.12析构函数 6.13析构对象数组 6.14总结 第7章 循环语句 ...

    零起点学通C++学习_多媒体范例教学代码

    13.9.1 在虚函数中调用成员函数 13.9.2 3种调用虚函数的方式比较 13.10被继承的虚函数仍然是虚函数 13.11系统是如何调用虚函数的 13.12在虚函数中使用成员名限定 13.13虚析构函数 13.14总结 第14章 数组 ...

    传智播客扫地僧视频讲义源码

    06_课堂答疑类中写成员函数_调用的时才会执行 07_程序设计方法发展历程 08_C语言和C++语言的关系_工作经验分享 09_namespace和iotream 10_实用性加强_register增强_检测增强 11_struct关键字类型增强 12_c++类型类型...

Global site tag (gtag.js) - Google Analytics