指向Member Function 的指针

Code:
  1. class A   
  2. {   
  3.   public:   
  4.     void f(){cout << "A::f() called." << endl;}   
  5.     static void g(){cout << "A::g() called." << endl;}   
  6.     virtual void h(){cout << "A::h() called." << endl;}   
  7. };  

1 指向普通函数的指针

Code:
  1. typedef void (A::*PFUN)(void);   
  2. PFUN p = &A::f;   
  3. A t;   
  4. (t.*p)();  

该成员函数的指针与普通函数指针定义不同,这里加上了类名和作用于操作符。用于表示该函数指针指向一个类的成员函数。

若我们输出p的值。即:

Code:
  1. cout << p << endl;  

在 Code::Blocks  ,CFree  ,GCC  ,VS2008 上的输出结果均为1. 所有的编译器均吧普通成员函数的地址给隐藏了。我们好像无法获得他们的地址.确实是这样吗?我不甘心,我又用C语言的输出方式测试了一下,即:

Code:
  1. printf("%d/n",p);  

这样成功了,以上所有编译器均得到了一个类似地址的东西。对此我为了再确定一下,我又紧挨着定义了一个普通成员函数,然后输出该函数的地址。结果两个输出值是连续的。由此可见我们通过此方法得到了普通成员函数的地址。呵呵。。看来C语言形式即使在C++中有时候也真的很有用啊。。。

2 指向静态成员函数的指针

Code:
  1. typedef void (*PF)(void);   
  2. PF q = A::g;   
  3. q();   
  4. cout << q << endl;  

静态成员函数的指针和普通函数的指针一样。不过它在赋值的时候需要加上类名和作用于操作符。并且在赋值的时候A::g 和 &A::g的效果好像是一样的。都没有错误。然而这里有点奇怪的是该函数地址的输出结果:

Code::Blocks 输出的结果为1.       GCC输出的结果为一个地址。 CFree (MinGw内核) 输出为1  VS2008 输出一个地址。

看来不同的编译器对该成员函数的处理方式是不一样的。Code::Blocks  和 CFree (MinGw内核)  明显隐藏了函数的地址。

好,那我们再用C语言的方式在这两个编译器中测试一下。可以了,我们同样得到了函数的地址。不过我们还是不放心啊。。C语言这种形式到底准不准啊。。我们再在另外两个可以输出地址的编译器中做下测试:

Code:
  1. typedef void (*PF)(void);   
  2. PF q = &A::g;   
  3. q();   
  4. cout << (int)q << endl;   
  5. printf("%d/n",q);  

结果是一样的。说明我们准确的获得了地址。

贴一下更改过的整体代码:

Code:
  1. #include <iostream>   
  2. using namespace std;   
  3. class A   
  4. {   
  5.   public:   
  6.     void f(){cout << "A::f() called." << endl;}   
  7.     void k(){cout << "A::k() called." << endl;}   
  8.     static void g(){cout << "A::g() called." << endl;}   
  9.     virtual void h(){cout << "A::h() called." << endl;}   
  10. };   
  11. int main()   
  12. {   
  13.     typedef void (A::*PFUN)(void);   
  14.     PFUN p = &A::f;   
  15.     printf("%d/n",p);   
  16.     p = &A::k;   
  17.     printf("%d/n",p);   
  18.     A t;   
  19.     (t.*p)();   
  20.     typedef void (*PF)(void);   
  21.     PF q = &A::g;   
  22.     q();   
  23.     cout << (int)q << endl;   
  24.     printf("%d/n",q);   
  25.     return 0;   
  26. }  

不过经过这三个函数(f(),k(),g())地址的对比,各个编译器对函数的存储方式也是有差别的。。

在Code::Blocks 中:

 f() ---g()---k()

GCC中:

f()---k()---g()

VS2008中:

f()---k()---g()

CFree(MingW)

f()---k()---g()

由此我怀疑是Code::Blocks IDE的问题。因为其他三个IDE的存储顺序相同,并且按照函数声明的顺序来存储的。相反Code::Blocks 的存储方式就令人感觉困惑了。。

3 指向虚函数的指针

在类A中定义了如下虚函数:

Code:
  1. virtual void h(){cout << "A::h() called." << endl;}   
  2. virtual void t(){cout << "A::t() called." << endl;}   
  3. virtual void m(){cout << "A::m() called." << endl;}  

指向虚函数的指针的定义和普通成员函数指针的定义一样,使用方法也是一样的:

Code:
  1. typedef void (A::*PFUN)(void);   
  2. PFUN p ;   
  3. A *n = new A;   
  4. p = &A::h;   
  5. (n->*p)();   
  6. cout << p << endl;   
  7. printf("%d/n",p);   
  8. p = &A::t;   
  9. (n->*p)();   
  10.  cout << p << endl;   
  11.  printf("%d/n",p);   
  12. p = &A::m;   
  13. (n->*p)();   
  14. cout << p << endl;   
  15. printf("%d/n",p);  

使用C++的输出方式输出虚函数的地址:

所有编译器的结果相同 均为 1.对于这个1 《深度探索C++对象模型》上说是索引值。我不认为如此,若是索引值的话,这三个函数的输出结果因为为 1 ,2,3 或是 0,1 ,2.而不是均为1.这里的1显然是IDE隐藏虚函数地址来用的。

使用C语言的输出方式:

有两种结果:

Code::Blocks 和 CFree(MinGW) 输出的结果为 1 ,4 ,9 。显然也不是索引值。不过根据推断,我们可以把它理解为偏移量。即各个虚函数的地址在虚函数表中的偏移量。

GCC 和 VS2008 输出的结果为真正的地址。然而奇怪的是这两个结果,即这三个虚函数的地址顺序相反。。呵呵。。估计是IDE自身的问题吧。

4 多重继承下指向Member Function的指针

为了让指向成员函数的指针能够支持多重继承和虚拟继承。stroup定义了一个结构体:

Code:
  1. struct _mptr   
  2. {   
  3.     int delta;   
  4.     int index;   
  5.     union  
  6.     {   
  7.         ptrtofunc faddr;   
  8.         int v_offset;   
  9.     };   
  10. };  

index 是虚函数表的索引。faddr是虚函数的地址。若成员函数非虚,index = -1;

故我们的调用操作:

Code:
  1. (ptr->*pmf)();  

会变成:

Code:
  1. (pfm.index < 0) ? (*pfm.faddr)(ptr): (*ptr->vptr[pmf.index](ptr));  

 这种方法有个缺点,就是对于每个成员函数的调用都要经过上面的判断。于是微软就利用了另一种技术thunk技术。而不去做上面的检查了。对此就不再多说了。。

5 指向Member Function 的多态行为

即我们通过调用函数指针间接的调用函数可以产生多态行为吗?我们不妨做一个测试:

Code:
  1. #include <iostream>   
  2. using namespace std;   
  3. class A   
  4. {   
  5. public:   
  6.     virtual void h(){cout << "A::h() called." << endl;}   
  7.     virtual void t(){cout << "A::t() called." << endl;}   
  8.     virtual void m(){cout << "A::m() called." << endl;}   
  9. };   
  10. class B:public A   
  11. {   
  12. public:   
  13.     void h(){cout << "B::h() called." << endl;}   
  14.     void t(){cout << "B::t() called." << endl;}   
  15. };   
  16. int main()   
  17. {   
  18.     typedef void (A::*PFUN)(void);   
  19.     PFUN p = &A::h;   
  20.     A *Y = new A;   
  21.     A *q = new B;   
  22.     (Y->*p)();   
  23.     (q->*p)();   
  24.     p = &A::t;   
  25.     (Y->*p)();   
  26.     (q->*p)();   
  27.     p = &A::m;   
  28.     (Y->*p)();   
  29.     (q->*p)();   
  30.     return 0;   
  31. }  

输出结果:

 

结果很明显,实现了多态。为什么能够实现多态呢?这又说明了什么呢?很明显,说明我们获得的虚函数的地址并不是真正的虚函数的地址。1,4,9代表虚函数表中的offset。GCC 和 VS2008 得到的地址是存储虚函数地址的地址,即虚函数表的某个slot的地址。故他们在运行期可以肯据实际对象调用相应的函数,从而实现多态行为。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值