C++中虚函数工作原理和(虚)继承类的内存占用大小计算-1

上一篇 / 下一篇  2012-08-21 09:25:46 / 个人分类:C++

51Testing软件测试网J,[Q[Jn8wEOrH!P

  一、虚函数的工作原理51Testing软件测试网i.C.Iy0].K M k

51Testing软件测试网T,fI0x2}]0F d s

   每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就会为这个类创建一个虚函数表(VTABLE)保存该类所有虚函数的地址,其实 这个VTABLE的作用就是保存自己类中所有虚函数的地址,可以把VTABLE形象地看成一个函数指针数组,这个数组的每个元素存放的就是虚函数的地址。 在每个带有虚函数的类 中,编译器秘密地置入一指针,称为vpointer(缩写为VPTR),指向这个对象的VTABLE。 当构造该派生类对象时,其成员VPTR被初始化指向该派生类的VTABLE。所以可以认为VTABLE是该类的所有对象共有的,在定义该类时被初始化;而 VPTR则是每个类对象都有独立一份的,且在该类对象被构造时被初始化。

Abc8l"g4iwbZ:u0

9v B%d+O+U"J fm:R0  通过基类指针做虚函数调 用时(也就是做多态调用时),编译器静态地插入取得这个VPTR,并在VTABLE表中查找函数地址的代码,这样就能调用正确的函数使晚捆绑发生。为每个 类设置VTABLE、初始化VPTR、为虚函数调用插入代码,所有这些都是自动发生的,所以我们不必担心这些。51Testing软件测试网2w~ Y;Ag AQ

2_,nk7X&^0#include<iostream>
U3M/K7?rAc:V0using namespace std;

1W7?&?;T1u_051Testing软件测试网Vh UV@g

class A51Testing软件测试网%J8Np&~|8XkH
{
m(}5e_%p#T0public:51Testing软件测试网'u!M'b r(k'aMtZ
 virtual void fun1()
6[4b i6KJ ju0 {
E/w v.Yh0w0  cout << "A::fun1()" << endl;51Testing软件测试网%v7@/Rh8J
 }
!t$a;on7x rM0 virtual void fun2()
(| e%f{:k|*`$fj0 {
Vp0`{ok(`'Q$^0  cout << "A::fun2()" << endl;
@ UV {LjO*DI0 }51Testing软件测试网^ F/j5^!Pj Z
};
51Testing软件测试网0i3]k2[m } zS/~ e

3mX*h/n Z0class B : public A
_+J,b M G0{51Testing软件测试网.]u4Hpt!v^l
public:
ap'IFn}%G0 void fun1()51Testing软件测试网Ff9X7J*\6Z
 {51Testing软件测试网*`7v)F:|s x;av
  cout << "B::fun1()" << endl;51Testing软件测试网"k,LNqm:KQt Cg t
 }
|Dql/K:w D ze t0 void fun2()
$lj tLF0 {51Testing软件测试网[_k:@ S S
  cout << "B::fun2()" << endl;
E z"qU2r/VH/d0 }51Testing软件测试网hLX ?'tnL| m
};
51Testing软件测试网!gw%? K)f)h`J!T

51Testing软件测试网1v ` ]2UHUx\~

int main()
6pd`j b D9]0{51Testing软件测试网??`3B)Dh[;I)va
 A *pa = new B;
!X0K \Rj0 pa->fun1();
#s6r(^9y \*AQ;I0 delete pa;
51Testing软件测试网a%fM-j+m9Sk

c$v F ln0 system("pause");
%b4A0jH/lU0 return 0;51Testing软件测试网6TB ?h(mBq
}
51Testing软件测试网~tNV*o ~2[5A

51Testing软件测试网3SV*uBk _9^

   毫无疑问,调用了B::fun1(),但是B::fun1()不是像普通函数那样直接找到函数地址而执行的。真正的执行方式是:首先取出pa指针所指向 的对象的vptr的值,这个值就是vtbl的地址,由于调用的函数B::fun1()是第一个虚函数,所以取出vtbl第一个表项里的值,这个值就是 B::fun1()的地址了,最后调用这个函数。因此只要vptr不同,指向的vtbl就不同,而不同的vtbl里装着对应类的虚函数地址,所以这样虚函 数就可以完成它的任务,多态就是这样实现的。51Testing软件测试网"sHP+T2e;l)y

51Testing软件测试网2clf)e,B"y

  而对于class A和class B来说,他们的vptr指针存放在何处?其实这个指针就放在他们各自的实例对象里。由于class A和class B都没有数据成员,所以他们的实例对象里就只有一个vptr指针。

+EvT:bJ F051Testing软件测试网}uN0fGd^*s

  含有虚函数的对象在内存中的结构如下:

1\ie IuxjZm0
class A
I vOc V0X h0{
,NP0p'wgP-z0private:51Testing软件测试网OAH4y~7]
 int a;51Testing软件测试网5WL"kV-^(@Nt
 int b;51Testing软件测试网g-P%V@#_Z
public:
&f7w] c"|0 virtual void fun0()
s\V}5Cf"c0 {51Testing软件测试网&}F1pZZUAi
  cout<<"A::fun0"<<endl;
,ir sc d#P3SUD8N0 }51Testing软件测试网;^Up:oM)d%DE
};
51Testing软件测试网PzC%R&V ur Y

51Testing软件测试网9J,B3_.G$sP

  1、直接继承

J ki?*E1})K(V#U9T;m0

  那我们来看看编译器是怎么建立VPTR指向的这个虚函数表的,先看下面两个类:51Testing软件测试网e/M0f:SiEC

51Testing软件测试网9qo E o1shJzO X$u

51Testing软件测试网 xe!|#vbb

class base51Testing软件测试网pA2?.uc
{
T3IR0ic6D(@0private:51Testing软件测试网aV6@"g`T*_W'VkE
 int a;
)n3c{.I7Nd0public:
r|H1Cni ot,SS0 void bfun()51Testing软件测试网-~,W%|qXq L
 {
?1fcOw6J0 }51Testing软件测试网Y0]6JQ)Z1?7x
 virtual void vfun1()51Testing软件测试网/t1e;Sr#Q'eW y X.e
 {
2akOCI3j8r'^D0 }51Testing软件测试网$G f0vx'k ^
 virtual void vfun2()
*e.P#HkQ"j0 {
HgqKRB-U0 }51Testing软件测试网#L&p)sn6?Q
};

^0W7Ra/}Io|6|051Testing软件测试网 OiuOSy-Eo

class derived : public base51Testing软件测试网B3al-H T9hHL I^#w
{51Testing软件测试网 JsD0W;Iv&l&u
private:
L*lvM v.mlBi9p0 int b;51Testing软件测试网&{tf!a R!wp l
public:51Testing软件测试网@|edC.?@
 void dfun()
M)kl5aZ+g3Vri0 {
.cP~6Bvjt;[0 }
Rt1{'Je(XLH0 virtual void vfun1()
#Q,Y7X)~6xT0 {
bn-|J(i'AP&x"f H z0 }51Testing软件测试网w/TA9|cJ Q1e
 virtual void vfun3()51Testing软件测试网(CU4m9p1m
 {51Testing软件测试网u9j$Cx/d%w/Y
 }51Testing软件测试网g/f6KW)M5\
};
51Testing软件测试网;h$sq`kv

  两个类的VPTR指向的虚函数表(VTABLE)分别如下:

v#Gnb_0

  base类51Testing软件测试网Y lx Tk~
                       ——————
fPA.y$fsJp0  VPTR——>    |&base::vfun1 |
/hHp$@+be0                        ——————51Testing软件测试网8t#X;}GUN
                      |&base::vfun2 |51Testing软件测试网%rBuKMf9iR;w
                      ——————51Testing软件测试网n M!G \T9q
       51Testing软件测试网4r+Q!C],\
  derived类
%zX4~B'wKXE$v0                        ———————51Testing软件测试网%At$b7v)I*rr6Jo
  VPTR——>    |&derived::vfun1 |
1T*B!gb+J.lz0{ J4Y{0                       ———————
S&AnE"jW } N6?v0                     |&base::vfun2     |51Testing软件测试网a v&X`5W6Ks
                      ———————51Testing软件测试网%y }4iRG(J
                     |&derived::vfun3 |51Testing软件测试网!I ]1A2xj*X+wdp K O
                      ———————
51Testing软件测试网&p-qqD-x1fAf

  每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就为这个类创建一个VTABLE,如上图所 示。在这个表中,编译器放置了在这个类中或在它的基类中所有已声明为virtual的函数的地址。如果在这个派生类中没有对在基类中声明为virtual 的函数进行重新定义,编译器就使用基类 的这个虚函数地址。(在derived的VTABLE中,vfun2的入口就是这种情况。)然后编译器在这个类中放置VPTR。当使用简单继承时,对于每 个对象只有一个VPTR。VPTR必须被初始化为指向相应的VTABLE,这在构造函数中发生。

U7T/\S,xa$~0

  一旦VPTR被初始化为指向相应的VTABLE,对象就"知道"它自己是什么类型。但只有当虚函数被调用时这种自我认知才有用。

WyH8]2Jr0

  2、虚继承

.NM\F JOkt)B` M0

  这个是比较不好理解的,对于虚继承,若派生类有自己的虚函数,则它本身需要有一个虚指针,指向自己的虚表。另外,派生类虚继承父类时,首先要通过加入一个虚指针来指向父类,因此有可能会有两个虚指针。

]u7zK)o6B O0

TAG:

 

评分:0

我来说两句

Open Toolbar