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

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

9L:A lW bT%L7i0  一、虚函数的工作原理51Testing软件测试网&Vww)m_5\l]1?o{

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

6sJX/pQ0

[ |8u0Sfh"L'Lz1d5W0  通过基类指针做虚函数调 用时(也就是做多态调用时),编译器静态地插入取得这个VPTR,并在VTABLE表中查找函数地址的代码,这样就能调用正确的函数使晚捆绑发生。为每个 类设置VTABLE、初始化VPTR、为虚函数调用插入代码,所有这些都是自动发生的,所以我们不必担心这些。

s};`#W.Gj0

.k dt*aQ0#include<iostream>
OZ GX/w(]fz0using namespace std;
51Testing软件测试网%M5Gs0bq i }^ H q

H6clx |Ui;e-RO0class A51Testing软件测试网Hb;Q e:^ m#N;[v
{51Testing软件测试网Fdw Z|y b
public:
&Ce:ex-q;vY+Mt0 virtual void fun1()
@Ju8i^-M {7Bm0 {51Testing软件测试网 ];E0@*Y-~U)T
  cout << "A::fun1()" << endl;51Testing软件测试网)T _o$M1?z)]kY
 }51Testing软件测试网 j%E`D5B%Ly,[
 virtual void fun2()51Testing软件测试网7D(y#HY }{2W
 {51Testing软件测试网7^V pF:Z#[$s
  cout << "A::fun2()" << endl;51Testing软件测试网&f|gSK
 }51Testing软件测试网|/wp It Zy!ff
};
51Testing软件测试网 C&~]{5s.~

51Testing软件测试网n*eF8^a~\0J

class B : public A
w9sR ISu#pL0{
i"}$T#eM't P-jx0public:51Testing软件测试网cb3E |Gz)e p` y
 void fun1()51Testing软件测试网:s Q0\(^p2Q-AA1s,u
 {
)PmgJ N/NI0  cout << "B::fun1()" << endl;
w8\n)f&J,_0 }51Testing软件测试网#q$y?I;C
 void fun2()
Fxp)G?:V,X0 {51Testing软件测试网5bJ!v%N5k'Z X n7p
  cout << "B::fun2()" << endl;51Testing软件测试网.cT&_3s3W@Yc[
 }51Testing软件测试网 V/_)F\/Iv
};

|JINn\N051Testing软件测试网~Cr Lu

int main()
:pr-PIQN:_0~4d0{
GC4|lIB#AFOa0 A *pa = new B;51Testing软件测试网6soe{Z/\
 pa->fun1();
mDCIA V'o0 delete pa;
51Testing软件测试网Rn2|M2V8f.o:`

51Testing软件测试网5aqEO|[

 system("pause");51Testing软件测试网;S*P n$um
 return 0;51Testing软件测试网0d3@ PL2OT5\ f
}
51Testing软件测试网Srq{*hn3e4|

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

U HF:lP$_rmK#EF0

5Ej!|'].X'~*`9w2E#[}0  而对于class A和class B来说,他们的vptr指针存放在何处?其实这个指针就放在他们各自的实例对象里。由于class A和class B都没有数据成员,所以他们的实例对象里就只有一个vptr指针。51Testing软件测试网4r,HW zI3A S

51Testing软件测试网e?vD!{xU

  含有虚函数的对象在内存中的结构如下:51Testing软件测试网L%FQ'fd!Yu

class A51Testing软件测试网*Z'Eow rn{U
{51Testing软件测试网cm2u*K ~_+Mdr
private:
2] ??)^A/p*i0 int a;51Testing软件测试网U'v b/r9lP)v
 int b;
D6L'B7bw4k7y,E0public:51Testing软件测试网P^+PK5h&z/D9A py2B
 virtual void fun0()
y8z QR3^pSJ:_a0 {51Testing软件测试网1|C-RCPK
  cout<<"A::fun0"<<endl;51Testing软件测试网Uu t+RCh
 }
"W7gZo4s9uxxX0};

#H0G4`g%rfI l0

51Testing软件测试网X+]4fk.z r6g

  1、直接继承

K/G0b_:ThB:P0

  那我们来看看编译器是怎么建立VPTR指向的这个虚函数表的,先看下面两个类:

4R|e \NU2L0

KZQ Z'H0

bQ8S-fCB?hw1U-Xs0class base
/`+dm)VP/W `0{51Testing软件测试网 @;_1Y%ac$x{@M
private:
/_[$B S(h*M!~$x$_S6} m0 int a;51Testing软件测试网G*G?8d,m
public:
~&~8H3B4LA |Q$R0 void bfun()
1Q{V-D-o7wF~0 {51Testing软件测试网k Z2Y h.dA
 }
i'M_ x4PK0 virtual void vfun1()51Testing软件测试网e'DdT%z8y |
 {
W E'l3n3c+Ug0 }51Testing软件测试网G1K_1o*L/vb
 virtual void vfun2()
Yubt2fn5@p6s0 {51Testing软件测试网K D b#V {R `G1{
 }
pm%ti`7u]9\B#b,@0};
51Testing软件测试网F&~H%\f;Q X"z

51Testing软件测试网9?g8LG8W0o)g

class derived : public base
"?pF |2b&n b3z0{
7YLY\u o0private:
fn l5Mmf)x Ui0 int b;51Testing软件测试网3f1J$X}%B
public:51Testing软件测试网O.K8B WD'u
 void dfun()51Testing软件测试网)qs%q9xwr
 {
2H!O'g"w F g-s0 }
H,L;A[M!c6b0 virtual void vfun1()51Testing软件测试网2U2ZFf}+u
 {51Testing软件测试网"V P}v8c
 }
sU|"g4b$bg0 virtual void vfun3()51Testing软件测试网!R&I h!h$]"B2qp
 {51Testing软件测试网X0y)q'~Rzg#|
 }
t/m.PX)N-DN0};
51Testing软件测试网C-rp"{1eJ*{g

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

j$l`Y"RN9p0

  base类
B;v.ZpEay3I;N0                       ——————
|[;@5QT8K_.M p0  VPTR——>    |&base::vfun1 |
6K9p$tw@8l]0                        ——————
_3VPW/OrUx0                      |&base::vfun2 |
-yX0yQ(A1?(]0                      ——————
8i+wi"O1h7?%v0       51Testing软件测试网VoJ~N.hl)I9i S
  derived类
G'e7o~:j6B0                        ———————
G@VD9\l0  VPTR——>    |&derived::vfun1 |
C'CE)Q?7K-y0Qv0                       ———————51Testing软件测试网@)RL!I pt9|1zic
                     |&base::vfun2     |51Testing软件测试网!A/mZ"pjc*q$A2t
                      ———————
y/q_TsP0                     |&derived::vfun3 |51Testing软件测试网H/K [Z(P2~i
                      ———————
51Testing软件测试网DP@AA4QRG

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

T\\ m&E/r]h*b5k:G~0

  一旦VPTR被初始化为指向相应的VTABLE,对象就"知道"它自己是什么类型。但只有当虚函数被调用时这种自我认知才有用。51Testing软件测试网f:M2Csv/z

  2、虚继承51Testing软件测试网-l/{D6PA%x

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


TAG:

 

评分:0

我来说两句

Open Toolbar