[转载] 类成员函数指针深度分析

原文地址:http://blog.sina.com.cn/s/blog_601529b70100errd.html

 

 

今天突然想起了类成员函数指 针,于是想到几个问题:

1. 一个类的成员函数指针可以转化为另一个类的成员函数指针么?

2. 类成员函数指针与普通函数指针真的完全不一样么?

接下来的讨论将回答这两个问题! (有关类成员函数指针概念请参考相关资料....)

 

一,类虚成员函数指针的内部实现:

class CTest
{
public:

 virtual void ShowMsg()
 {
  cout<<"call ShowMsg In CTest Class..."<<endl;
 }
};

void main()

{

    typedef void (CTest::*MFT_virtual_fun_ptr)();
    MFT_virtual_fun_ptr my_virtual_fun_ptr=&CTest::ShowMsg;

    CTest t;

    (t.*my_virtual_fun_ptr)();

    system("pause");

}

 

程序运行后输出call ShowMsg In CTest Class...

分析汇编源代码,发现给my_virtual_fun_ptr赋值的汇编代码为:

mov  DWORD PTR _my_virtual_fun_ptr$[ebp], OFFSET ??_9CTest@@$BA@AE

这里多出来一个莫名其妙的符号:??_9CTest@@$BA@AE。继续查找,发现原来它是一个函数(应该是编译器自动产生的一个辅助函数):

; COMDAT ??_9CTest@@$BA@AE
_TEXT SEGMENT
??_9CTest@@$BA@AE PROC     ; CTest::`vcall'{0}', COMDAT
  00000 8b 01   mov  eax, DWORD PTR [ecx]
  00002 ff 20   jmp  DWORD PTR [eax]
??_9CTest@@$BA@AE ENDP     ; CTest::`vcall'{0}'

     可以看到,它实现的功能仅仅是从ECX得到this指针,然后跳转到类的虚函数表中对应的函数入口处!而且通过这种方式,调用同一个类的不同虚成员函数, 编译器会为各个不同虚成员函数的调用,各自产生一个类似的函数。例如:如果类有多个虚函数,我们通过成员函数指针去调用第二个虚函数,那么产生的类似函数 第二条指令为:jmp  DWORD PTR [eax+4]。原因是得偏移到虚函数表的第二个表项,也就是第二个虚函数的地址!!

 

通过以上的分析,虚成员函数的调用方式是编译器产生一个中间函数来管理this指针的传递!!所以只要this指针传递正确,那么调用就可以正确实现。这 里没涉及到不同的类的情况,所以我猜测第一个问题的答案是,可以进行转化,于是写了下面的代码:

class CTest
{
public:

 virtual void ShowMsg()
 {
  cout<<"call ShowMsg In CTest Class..."<<endl;
 }
};

 

class CTestAnother
{
public:
 virtual void ShowMsg(string msg)
 {
  cout<<"call ShowMsg In CTestAnother Class..."<<msg.c_str()<<endl;
 }
};

 

void main()

{

    typedef void (CTest::*MFT_virtual_fun_ptr)();
    MFT_virtual_fun_ptr my_virtual_fun_ptr=&CTest::ShowMsg;

    CTestAnother td;
    (td.*(void (CTestAnother::*)(string))my_virtual_fun_ptr)("haha...");

 

    system("pause");

}

程序运行后的输出:call ShowMsg In CTestAnother Class...haha...

哈哈,转化成功,而且还是把无参的成员函数指针,转化成了有参的成员函数指针。

so....对于虚成员函数指针的情况,因为它的内部实现只是正确传递this指针,所以不管类之间是继承还是毫无关系,都可以进行指针 转化。

 

对于非虚成员函数,由于它的内部特性,不能进行类之间的成员函数指针转化。原因请看后面的分析!

二,非虚成员函数指针的内部实现:

class CTest
{
public:

 void ShowMsg()
 {
  cout<<"call ShowMsg In CTest Class..."<<endl;
 }


};

void main()

{

    typedef void (CTest::*MFT_fun_ptr)();
    MFT_virtual_fun_ptr my_fun_ptr=&CTest::ShowMsg;

    CTest t;

    (t.*my_fun_ptr)();

    system("pause");

}

程序的输出为:call ShowMsg In CTest Class...

对于程序的输出毫无疑问,看看对my_fun_ptr赋值的汇编代码:

mov  DWORD PTR _my_fun_ptr$[ebp], OFFSET ?ShowMsg@CTest@@QAEXXZ ; CTest::ShowMsg

发现编译器是直接使用的CTest类的ShowMsg函数地址....也就是不涉及到类的信息,这种特性和普通的函数完全一样(这里指的普通函数指非类的 成员函数)

既然特性和普通函数没有区别,那是不是代表我可以把类的成员函数,强制转化为一个普通函数来使用呢?答案是肯定的,哈哈.....

class CTest
{
public:

 CTest(int a):x(a){}

 

 void ShowMsg()
 {
  cout<<"call ShowMsg In CTest Class..."<<x<<endl;
 }

 

private:

  int x;
};

void main()

{

    typedef void (CTest::*MFT_fun_ptr)();
    MFT_virtual_fun_ptr my_fun_ptr=&CTest::ShowMsg;

    CTest t(10);

    (t.*my_fun_ptr)();

 

    int addr;
    __asm                 //这里使用嵌入汇编的目的是进行不可能的类型转化
    {
     MOV EAX,my_fun_ptr
     MOV addr,EAX
    }

 

     typedef void (*NFT_fun_ptr)();   //普通函数指针
      NFT_fun_ptr sdf=reinterpret_cast<NFT_fun_ptr>(addr);  //再一次强制转化

      CTest*p=&t;   //传递this指针
      __asm MOV ECX,p

      sdf();

      system("pause");

}

程序的输出为:call ShowMsg In CTest Class...10

                    call ShowMsg In CTest Class...10

哈哈....成功把类的成员函数指针,转化为普通函数指针来调用了!!

这里要说明一下的是,我试过各种办法,无法直接把my_fun_ptr强制转化为NFT_fun_ptr类型。所以只有借助嵌入汇编!不过后来想到另一种 方法也可以实现这种转化:

union
 {
  NFT_fun_ptr addr;
  MFT_fun_ptr ptr;
 }haha;
 haha.ptr=my_fun_ptr;

哈哈,这里利用了union共享内存的特性,然后直接访问addr成员,就完成了所需要的转化!!

这种方法很不错哦,可以推广到许多无法通过普通手段进行类型转化的情况!

 

所以对于第二个问题的回答是:两者没什么区别!!

 

总结一下:

虚成员函数指针,可以在不同类之间进行任意转化,而非虚成员函数指针,由于实现的方式与普通函数一样,所以无法进行不同类之间的转化!! 非虚成员函数指针,可以转化为普通函数指针,而且实现方式与普通函数指针完全一样(唯一不一样的是this指针的传递)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值