- class A
- {
- public:
- void f(){cout << "A::f() called." << endl;}
- static void g(){cout << "A::g() called." << endl;}
- virtual void h(){cout << "A::h() called." << endl;}
- };
1 指向普通函数的指针
- typedef void (A::*PFUN)(void);
- PFUN p = &A::f;
- A t;
- (t.*p)();
该成员函数的指针与普通函数指针定义不同,这里加上了类名和作用于操作符。用于表示该函数指针指向一个类的成员函数。
若我们输出p的值。即:
- cout << p << endl;
在 Code::Blocks ,CFree ,GCC ,VS2008 上的输出结果均为1. 所有的编译器均吧普通成员函数的地址给隐藏了。我们好像无法获得他们的地址.确实是这样吗?我不甘心,我又用C语言的输出方式测试了一下,即:
- printf("%d/n",p);
这样成功了,以上所有编译器均得到了一个类似地址的东西。对此我为了再确定一下,我又紧挨着定义了一个普通成员函数,然后输出该函数的地址。结果两个输出值是连续的。由此可见我们通过此方法得到了普通成员函数的地址。呵呵。。看来C语言形式即使在C++中有时候也真的很有用啊。。。
2 指向静态成员函数的指针
- typedef void (*PF)(void);
- PF q = A::g;
- q();
- cout << q << endl;
静态成员函数的指针和普通函数的指针一样。不过它在赋值的时候需要加上类名和作用于操作符。并且在赋值的时候A::g 和 &A::g的效果好像是一样的。都没有错误。然而这里有点奇怪的是该函数地址的输出结果:
Code::Blocks 输出的结果为1. GCC输出的结果为一个地址。 CFree (MinGw内核) 输出为1 VS2008 输出一个地址。
看来不同的编译器对该成员函数的处理方式是不一样的。Code::Blocks 和 CFree (MinGw内核) 明显隐藏了函数的地址。
好,那我们再用C语言的方式在这两个编译器中测试一下。可以了,我们同样得到了函数的地址。不过我们还是不放心啊。。C语言这种形式到底准不准啊。。我们再在另外两个可以输出地址的编译器中做下测试:
- typedef void (*PF)(void);
- PF q = &A::g;
- q();
- cout << (int)q << endl;
- printf("%d/n",q);
结果是一样的。说明我们准确的获得了地址。
贴一下更改过的整体代码:
- #include <iostream>
- using namespace std;
- class A
- {
- public:
- void f(){cout << "A::f() called." << endl;}
- void k(){cout << "A::k() called." << endl;}
- static void g(){cout << "A::g() called." << endl;}
- virtual void h(){cout << "A::h() called." << endl;}
- };
- int main()
- {
- typedef void (A::*PFUN)(void);
- PFUN p = &A::f;
- printf("%d/n",p);
- p = &A::k;
- printf("%d/n",p);
- A t;
- (t.*p)();
- typedef void (*PF)(void);
- PF q = &A::g;
- q();
- cout << (int)q << endl;
- printf("%d/n",q);
- return 0;
- }
不过经过这三个函数(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中定义了如下虚函数:
- virtual void h(){cout << "A::h() called." << endl;}
- virtual void t(){cout << "A::t() called." << endl;}
- virtual void m(){cout << "A::m() called." << endl;}
指向虚函数的指针的定义和普通成员函数指针的定义一样,使用方法也是一样的:
- typedef void (A::*PFUN)(void);
- PFUN p ;
- A *n = new A;
- p = &A::h;
- (n->*p)();
- cout << p << endl;
- printf("%d/n",p);
- p = &A::t;
- (n->*p)();
- cout << p << endl;
- printf("%d/n",p);
- p = &A::m;
- (n->*p)();
- cout << p << endl;
- 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定义了一个结构体:
- struct _mptr
- {
- int delta;
- int index;
- union
- {
- ptrtofunc faddr;
- int v_offset;
- };
- };
index 是虚函数表的索引。faddr是虚函数的地址。若成员函数非虚,index = -1;
故我们的调用操作:
- (ptr->*pmf)();
会变成:
- (pfm.index < 0) ? (*pfm.faddr)(ptr): (*ptr->vptr[pmf.index](ptr));
这种方法有个缺点,就是对于每个成员函数的调用都要经过上面的判断。于是微软就利用了另一种技术thunk技术。而不去做上面的检查了。对此就不再多说了。。
5 指向Member Function 的多态行为
即我们通过调用函数指针间接的调用函数可以产生多态行为吗?我们不妨做一个测试:
- #include <iostream>
- using namespace std;
- class A
- {
- public:
- virtual void h(){cout << "A::h() called." << endl;}
- virtual void t(){cout << "A::t() called." << endl;}
- virtual void m(){cout << "A::m() called." << endl;}
- };
- class B:public A
- {
- public:
- void h(){cout << "B::h() called." << endl;}
- void t(){cout << "B::t() called." << endl;}
- };
- int main()
- {
- typedef void (A::*PFUN)(void);
- PFUN p = &A::h;
- A *Y = new A;
- A *q = new B;
- (Y->*p)();
- (q->*p)();
- p = &A::t;
- (Y->*p)();
- (q->*p)();
- p = &A::m;
- (Y->*p)();
- (q->*p)();
- return 0;
- }
输出结果:
结果很明显,实现了多态。为什么能够实现多态呢?这又说明了什么呢?很明显,说明我们获得的虚函数的地址并不是真正的虚函数的地址。1,4,9代表虚函数表中的offset。GCC 和 VS2008 得到的地址是存储虚函数地址的地址,即虚函数表的某个slot的地址。故他们在运行期可以肯据实际对象调用相应的函数,从而实现多态行为。