具有虚函数的类的大小。


一般的书上都说,虚函数是在运行时根据对象的实际类型“动态决定”函数入口。但什么是“动态决定”呢?实际上C++编译器在实现这个功能的时候,并非真的 等到虚函数被调用时才去判断这个对象是什么类型的。下面我用一个简单的图表来说明C++编译器到底干了些什么。假设有两个类

struct Base {
  virtual void f();
  virtual void g();
};


struct Derived : public Base {
  virtual void f();
  virtual void g();
};

Base 和 Derived 各有一个虚表,分别是 VTable_B 和 VTable_D ,那么编译器是怎么通过只用一个虚表指针来实现下面的“动态”调用呢?

Base *pB = new Derived();
pB->f();

先让我们看Base和Derived对象是怎么存储的,以及两个类的虚表结构

Base:                    VTable_B:
------------         -------------
|  vptr       |        |  f() 入口    |
+---------+        +----------+
|  Base的   |        |  g() 入口   |
|    数据     |        -------------
------------

Derived:                VTable_D:
------------        --------------
|  vptr       |        | f() 入口     |
+---------+        +----------+
|  Base的   |        | g() 入口    |
|    数据     |        -------------
+---------+
| Derived的|
|    数据     |
------------

pB 指针既可以指向 Base 对象,也可以指向 Derived 对象,所以 pB 本身是不确定的。但是,任何对象本身却从被 new 出来开始就是确定的,所以 Base 对象在构造时,编译器会往 vptr 中填上 VTable_B 的地址,而 Derived 对象在构造时,编译器会往 vptr 中填上 VTable_D 的地址

等到虚函数被调用的时候,也就是 pB->f() 这行语句被执行的时候,编译器并不需要知道 pB 到底是指向 Base 还是 Derived ,它只要直接用 vptr 就能找到正确的虚表和虚函数入口了,父类和子类的虚表结构是相似的,同一个虚函数入口在父表和子表的偏移量都是一样的

通过上面这些介绍,我想你应该能理解,为什么在单一继承的条件下,不管有多少层继承,每个对象只需一个 vptr 就行了。

多重继承的条件下,一个 vptr 行不行呢?

不行。多重继承的时候,虚函数既有来自父类1的,也有来自父类2的,所以这些虚函数入口是不能放在同一个虚表当中的。假设 Derived 除了 Base外,还继承 Base2,并且 Base2 中有两个虚函数 x() 和 y (),那么 Derived 对象的存储结构也许是这样的(只是大概,和具体编译器相关)。

Derived:                VTable_D:
------------        --------------
|  vptr       |        | f() 入口     |
+---------+        +----------+
|  Base的   |        | g() 入口    |
|    数据     |        -------------
+---------+
| vptr2      |          VTable_D2:
+---------+        -------------
| Base2的  |        |  x() 入口    |
|    数据     |        +-----------+
+---------+        | y() 入口     |
| Derived的|        -------------
|    数据     |
------------

posted @ 2012-03-11 16:01  kanego  阅读(1857)  评论(0编辑  收藏  举报