看书上说要是虚拟函数,而且调用的本身是一个指针或引用
则会对其进行转化,如:
MyClass *p; //p是一个复杂结构,其中的fun函数是一个virtual函数(父类的)
p->fun();
会对其进行转化成p->vptr[0](p) //相单于这样吧-_-#
可这里的vptr是MyClass本身的vptr还是父类的vptr??
___________________________________________
你说的情况不是多个VPTR,而是一个VTABLE的多个SLOT。
虚函数的实体地址存储在一个虚表(VTABLE)中,每个虚函数地址占一个SLOT。在对象实体中加入一个隐藏指针VPTR,它指向VTABLE。
______________________________________________
public:
virtual void method1(){}
virtual void method2(){}
virtual void method3(){}
};
void main(){
Test* t = new Test();
}
t->vptr->Test::method1
Test::method2
Test::method3
sizeof(Test)占一个字节,就是vptr的大小,VTABLE里面有三项。p->method1()就是:
p->vptr[0] ()
_______________________________________
至于当有多个vptr时,你做指针转换的时候,位置就已经调用好了。
所以vptr相对于指针本身指向的位置来说,偏移量永远是固定的——一般就是指针指向的位置,既所有的基类子对象都把vptr放头部。
________________________________________
是可以有多个VPTR的,通过当前指针区分VPTR,比如
class Derived: public A ,public B//假设A、B中都有虚函数,现有两个VPTR
Derived *pd = new Derived();//pd指向VPTRA
B* pb = dynamic_cast<B*>(pd);//pb指向VPTRB
_______________________________________
class T1
{
public:
virtual void vfunT1(){}
/*
______________
vptr---->| T1::vfunT1() |
---------------------------
*/
};
class T2
{
public:
virtual void vfunT2(){}
/*
____________
vptr---->| T2::vfunT2() |
------------------------
*/
};
class Inherit : public T1, public T2
{
public:
//实现两个虚拟函数
virtual vfunT1(){}
virtual vfunT2(){}
public:
//应该是一个表
/*
_______________
T1::vptr----->| Inherit::vfunT1() |
------------------------------
T2::vptr----->| Inherit::vfunT2() |
------------------------------
*/
};
int main()
{
Inherit *m = new Inherit();
m->vfunT1();
/*
转化成应该是m->vptr[0](m);可这里的vptr是T1的? T2的? 还是Inherit自己的
要是自己的话它的sizeof应该是sizeof(T1) + sizeof(T2) + vptr(4)才对
测试了一下结果就==sizoef(T1) + sizeof(T2),说明Inherit只有两个vptr
在Inherit再增加一个virtual function
virtual void vfunInherit(){}
大小没变,没新的vptr生成,既然是virtual function,那应该是要在表中末尾在添加1个项目
而且这时候问题出现了,假如再有一个MyClass继承自 Inherit, 而且也重写了virtual function
vfunInherit();
这个时候MyClass *ptrMyClass = new MyClass();
ptrMyClass->vfunInherit();//转化了后的vptr又是谁的
*/
}
____________________________________________________________
myclass在构造函数里对其vptr变量赋值为CMyClass的vtable的首地址
所以调用虚函数时,通过vptr和该虚函数在vtable里的索引可以在运行时刻查询到vtable里的
具体函数地址并调用之。
如果myclass没有override任何虚函数,则它的vtable就和基类的是一样的了.
至于vptr的个数,单重继承时最多一个,多重继承则可能有多个。
_____________________________________________________
#include <iostream>
using namespace std;
class T1
{
public:
int a;
};
class T2
{
public:
int a;
virtual void fun(){}
};
int main()
{
cout << sizeof(T1) << endl;
cout << sizeof(T2) << endl;
return 0;
}
_____________________________________________________________
vptr是属于对象的,只有vtable才是属于类的。
___________________________________________________________
sizeof(T2)=8
如果是:
class A
{
double d;
virtual method1();
};
sizeof(A)=16
或者
class B
{
double d;
virtual method1();
virtual method2();
virtual .........
};
sizeof(B)=16
class C
{
char ch;
virtual method1();
}
sizeof(C)=8;
个人推测:一般成员函数是不占大小的,对于一个和多个虚函数则只占4个字节(好像是地址的
大小(32位机子)),然后它和成员变量一起采用对齐的方式来求大小。
_________________________________________________________________
using namespace std;
typedef void (__cdecl *FUNPTR)(void* p);
void* getVPtr(void* pThis,int nIndex)
{
return(int*)(*((int*)pThis+nIndex));
}
void* getVTableFun(void* pThis,int nIndex,int nFun)
{
return (void*)(*((int*)(*((int*)pThis+nIndex))+nFun));
}
class A
{
public:
A()
{
out();
}
virtual void __cdecl funA()
{
cout<<" A::funA()"<<endl;
}
private :
void out()
{
cout<<"in class A:"<<endl;
cout<<"this="<<this<<endl;
cout<<"vptr="<<getVPtr(this,0)<<endl;
for(int i=0;i<4;i++)
{
cout<<"vtable["<<i<<"]="<<getVTableFun(this,0,i)<<endl;
}
cout<<endl;
}
};
class B
{
public:
B()
{
out();
}
virtual void __cdecl funB()
{
cout<<" B::funB()"<<endl;
}
private:
void out()
{
cout<<"in class B:"<<endl;
cout<<"this="<<this<<endl;
cout<<"vptr="<<getVPtr(this,0)<<endl;
for(int i=0;i<4;i++)
{
cout<<"vtable["<<i<<"]="<<getVTableFun(this,0,i)<<endl;
}
cout<<endl;
}
};
class /*__declspec(novtable)*/ C :public A,public B
{
public:
C()
{
out();
}
virtual void __cdecl funA()
{
cout<<" C::funA()"<<endl;
}
////如果C里不实现funB ,则该虚表内的函数地址为B::funB();
//virtual void __cdecl funB()
//{
//cout<<" C::funB()"<<endl;
//}
virtual void __cdecl funC()
{
cout<<" C::funC()"<<endl;
}
//打印出虚表地址和里面的函数地址
void out()
{
int i;
cout<<"in class C:"<<endl;
cout<<"this="<<this<<endl;
//虚表1
cout<<"vptr1="<<getVPtr(this,0)<<endl;
for( i=0;i<4;i++)
{
cout<<"vtable["<<i<<"]="<<getVTableFun(this,0,i)<<endl;
}
//虚表2
cout<<"vptr2="<<getVPtr(this,1)<<endl;
for( i=0;i<4;i++)
{
cout<<"vtable["<<i<<"]="<<getVTableFun(this,1,i)<<endl;
}
cout<<endl;
}
};
int main()
{
C c;
FUNPTR p;
//打印虚表1中的函数调用
for(int i=0;i<2;i++)
{
p=(FUNPTR)getVTableFun(&c,0,i);
p(&c);
}
//打印虚表2中的函数调用
for(int i=0;i<3;i++)
{
p=(FUNPTR)getVTableFun(&c,1,i);
p(&c);
}
return 1;
}
________________________________________________________________
在VC编译器中,虚表是在构造函数中建立的,先基类,再派生类,可以通过
__declspec(novtable)来禁止虚表的建立,实际上ATL中正是这么做的。编
译器通过改写类虚表中的函数地址来实现多态。因为C++成员函数是
thiscall,用函数指针不方便,所以我显式用了cdecl来测试。debug版中虚
表中的成员会以0结尾,release版中则否。多重继承时继承列表中最左侧的
基类和派生类公用一个虚表,其他的基类单独用一个虚表。
_________________________________________________________________
引自:http://community.youkuaiyun.com/Expert/topic/5268/5268806.xml?temp=3.215969E-03