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

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

X9`&h%i_5}{@(FB#W,l0  一、虚函数的工作原理51Testing软件测试网,K|c~L.pf,k8S

51Testing软件测试网h0vo.W0}

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

9[?I"Dq%NI&NV N0  通过基类指针做虚函数调 用时(也就是做多态调用时),编译器静态地插入取得这个VPTR,并在VTABLE表中查找函数地址的代码,这样就能调用正确的函数使晚捆绑发生。为每个 类设置VTABLE、初始化VPTR、为虚函数调用插入代码,所有这些都是自动发生的,所以我们不必担心这些。51Testing软件测试网"L-m\/jJo6e$R,U'HT

DIeOY*y U_0#include<iostream>51Testing软件测试网+?.p6O'H^rn
using namespace std;
51Testing软件测试网/C`k u4m%GL

51Testing软件测试网o*RB`s5l d

class A51Testing软件测试网Nl"rj+[3_ y!wq
{
1N6j MN0m iH^ R't0public:51Testing软件测试网G$U0xd-W
 virtual void fun1()51Testing软件测试网 tp KiQ6l#b
 {51Testing软件测试网o~n;E7K I7bm)aA
  cout << "A::fun1()" << endl;51Testing软件测试网P:k[h_ w)ud ~
 }
.^n d.~e yMG"F#nQ0 virtual void fun2()51Testing软件测试网!s~WXWu
 {
6R6N.Q_n1dH0  cout << "A::fun2()" << endl;51Testing软件测试网qd5^`K;Y3O Nh(?
 }
`/S#xh.[O5d UY5Ol0};
51Testing软件测试网 yy}&l"R

8c5r xWzi5[(}&G M5c0class B : public A
nCV0s`5|+W0{
:Aa)q1ux)R.S zIY~-B}S0public:51Testing软件测试网RY2x%P(~:m6c,b*C
 void fun1()
`*P;@4DS6W|,gZ0 {
7`RXQb+Vr0  cout << "B::fun1()" << endl;
FA/qDm^.]6Yc)}0 }
h*]F`:ty"kG.]9P0 void fun2()
"g/J9S-Mmv/P0 {
MKcn g(a`0  cout << "B::fun2()" << endl;
y"f(j.z`0 }51Testing软件测试网f,JdW+_
};
51Testing软件测试网 XAx ^eo%y R*dz

8xC|7vU8?p#J0int main()
5qQ3z{,d|E0{
f+MM,M mIt7VJ*H0 A *pa = new B;51Testing软件测试网&kuh+]e,j$s'^&~
 pa->fun1();51Testing软件测试网;Sh&M,K Af5C'J)k}3O
 delete pa;
51Testing软件测试网BoP{(zJ

51Testing软件测试网9C"XA;r2i`

 system("pause");51Testing软件测试网{8I3?.g#P(dZQE
 return 0;51Testing软件测试网 P]0u? pVw
}
51Testing软件测试网 nub},r4RcV

51Testing软件测试网TuK'Nx

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

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

UQ/FNC0F M WI051Testing软件测试网yU+`/gL'l

  含有虚函数的对象在内存中的结构如下:51Testing软件测试网M9uF,b;~6\

class A
7a)^,p No K:~?q0{51Testing软件测试网%TX9Itn5g
private:
N2^'@,|v%\_`0 int a;
Kr e:yNkk YY0 int b;51Testing软件测试网GA0R T {)s @P
public:51Testing软件测试网!IE2S0I4u)}/T0C)}
 virtual void fun0()
+_0ZKvl,w2s%hN"LE0 {51Testing软件测试网A.RkK)[2W$V9f+Gx
  cout<<"A::fun0"<<endl;51Testing软件测试网8g Y8d/uj-B
 }
1uRh(ftt aW0};

p iiCrpN0

51Testing软件测试网On#H z/d(o

  1、直接继承

b8LY C@N I0

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

;`a&] HQ N0

B9w,]E Y%K)P0class base
`d@qXU"T j0{51Testing软件测试网J,P+Vl.g
private:
)|:y4@Oq9V0 int a;
t{:@zFW~4x"FD0public:
(_+x%x1_}#nKB0 void bfun()51Testing软件测试网NY I_ H!Q
 {
8?r&wL}*|} K0 }51Testing软件测试网n"JL)Nj.Dv
 virtual void vfun1()
b y*j#f b#pN6FC |O0 {51Testing软件测试网,K*lug!g7E$G+L&s5J+i
 }51Testing软件测试网:ovFb_#Ex3x;\
 virtual void vfun2()
.w"oMT(qhWx0 {51Testing软件测试网U[&^"{,VMw&c
 }51Testing软件测试网!PV2tEP7\U_3w
};
51Testing软件测试网`&Jn kyV

51Testing软件测试网 {5ghpZ/mF w

class derived : public base51Testing软件测试网aBg2p!w imi
{51Testing软件测试网J$XH].z
private:
ssX$A%d8z0 int b;
^P9dp[m1WU0public:
wi%\^of&?%p0 void dfun()
w6ON-K,i0 {51Testing软件测试网 L pp1bO'z1y@
 }51Testing软件测试网/{KP1vc)^
 virtual void vfun1()
E5K~zW,G0 {51Testing软件测试网[ D3q$a6d8B(\-`${W
 }51Testing软件测试网9l({Q7DaS)w"H
 virtual void vfun3()51Testing软件测试网0d!p-R6VNd(K{
 {51Testing软件测试网J4n#B9{!_l!pN8R
 }
/kb jS\_l~ p0};
51Testing软件测试网8uHNt4@u[

  两个类的VPTR指向的虚函数表(VTABLE)分别如下:51Testing软件测试网6a0IQ)k T3x~

  base类51Testing软件测试网j/~r0r?/p#|X
                       ——————
8cg]dJ-@i~+@0  VPTR——>    |&base::vfun1 |51Testing软件测试网'eI!h8D|Fx#n!X4q
                        ——————51Testing软件测试网-N/aUQ3rgJ [
                      |&base::vfun2 |51Testing软件测试网~{L;vg`e
                      ——————51Testing软件测试网_)y b/AQYQw
       
JdOQE9r0  derived类
YQ@l1Ot1k0                        ———————51Testing软件测试网)p)T;x%DA1_!qn
  VPTR——>    |&derived::vfun1 |51Testing软件测试网f?F;G*\.]5E
                       ———————
0w"~C9R?*N/Y0                     |&base::vfun2     |51Testing软件测试网A%o ONFaD"w"Q9f`E
                      ———————51Testing软件测试网{;XR(tM i"h5Oi![
                     |&derived::vfun3 |
T@d I3r#_.H7B0                      ———————

#]K(U;Z,\8NufdM0

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

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

%u{9U eNR0

  2、虚继承

FX3CZ%aHq2bY0

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

T0~y(yrF2E7o,o0

TAG:

 

评分:0

我来说两句

Open Toolbar