一个复杂结构类中区分多个vptr

本文深入探讨了C++中虚函数的工作原理,包括虚表的结构与使用方式,以及多重继承情况下虚表的处理机制。并通过代码示例展示了如何访问和调用虚函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

看书上说要是虚拟函数,而且调用的本身是一个指针或引用
则会对其进行转化,如:
MyClass *p;        //p是一个复杂结构,其中的fun函数是一个virtual函数(父类的)
p->fun();
会对其进行转化成p->vptr[0](p)   //相单于这样吧-_-#
可这里的vptr是MyClass本身的vptr还是父类的vptr??

___________________________________________

你说的情况不是多个VPTR,而是一个VTABLE的多个SLOT。
虚函数的实体地址存储在一个虚表(VTABLE)中,每个虚函数地址占一个SLOT。在对象实体中加入一个隐藏指针VPTR,它指向VTABLE。

______________________________________________

 

class Test{
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(T1)=4
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位机子)),然后它和成员变量一起采用对齐的方式来求大小。

 

_________________________________________________________________

 

#include <iostream>

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值