通过一个实例来分析C++中虚函数的调用原理

虚函数是C++中一个重要的概念,搞清楚这个概念对于理解C++运行的内在机制有一定的帮助。下面通过一个例子来总结一下C++中虚函数的调用原理。

示例代码:


#include <iostream>
#include <cstdlib>

using namespace std;

class A
{
public:
void set(int i,int j)
{
x=i;
y=j;
}

virtual int get()
{
return x+y;
}
private:
int x;
int y;
};


class B:public A
{
public:
void set(int i,int j)
{
x=i;
y=j;
}

int get()
{
return x+y;
}
private:
int x;
int y;
};

int main(int argc,char *args[])
{
B b;
b.set(4,6);
B &rb=b;
cout<<rb.get()<<endl;
A a;
a.set(7,8);
cout<<a.get()<<endl;

A &ra=b;
cout<<ra.get()<<endl;
system("pause");
return EXIT_SUCCESS;
}



对于上述代码,下面是通过基类的引用来调用虚函数时(ra.get())所经历的几个阶段:
1. 开始调用ra.get(),在运行中引用ra可以判断到底指向了哪个对象(基类or派生类)
2. 取得对象的vtable的指针
3. 从vtable那里获得函数入口的偏移量,即得到要调用的函数的指针
4. 根据vtable的地址找到函数,并调用函数

一个函数说明为虚函数,表明在继承的类中重载这个函数时,到调用这个函数时应该确定调用哪个对象的这个函数。这也就是所谓多态的机制,多态使得我们可以通过基类的引用或指针来指明一个对象(包括其派生类的对象),当调用函数时可以自动判断调用的是哪个对象的函数。

C++编译器对普通函数和虚函数有着两种非常不同的处理方式。

对于[color=red]普通函数[/color]处理而言,特定的函数都会映射到特定的代码,无论是编译阶段还是链接阶段, 编译器都可以计算出要处理函数的地址,直接调用即可。

而对于[color=red]虚函数[/color]而言,被调用的函数不仅仅需要依据调用的特定函数,还需要依据调用对象的种类进行判断。通常情况下都是通过一个叫虚函数表(vtable)来实现的。

[b][color=brown]虚函数表的结构[/color][/b]
虚函数表是一个函数指针表,每个表项都指向一个函数。任何一个包含至少一个虚函数的类都会有这样一张表。需要注意的是vtable只包含虚函数的指针,没有函数体。实际上就是一个函数指针的数组。虚函数表既有继承性又有多态性。每个派生类的vtable继承了它各个基类的vtable,如果基类vtable中包含某一项,则它的派生类的vtable中也将包含同样的一项,但是这两项的值可能是不同的。如果派生类重载(override)了该项对应的虚函数,则派生类vtable的该项指向重载后的虚函数。若没有重载的话,则直接沿用基类的值。

特别注意的是,每一个类中仅有唯一的一个vtable,不是每个对象都有一个vtable。但是每个对象都有一个指向所属类的vtable的指针(此类包含虚函数的前提下)。每个对象额外增加了一个指针的大小,通常是4个字节。
此外,还需要注意的是基类和派生类使用的vtable是物理上独立的,即它们具有不同的地址。它们唯一的联系就是,当派生类没有实现基类虚函数的重载时,派生类会将自己vtable中对应函数的地址设定为基类函数的地址。


[color=brown]

Reference[/color]
1. 《从新手到高手C++全方位学习》 PP.245 系统是如何调用虚函数的
2. http://www.cppblog.com/ElliottZC/archive/2007/07/20/28416.aspx
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值