深入理解C++虚函数机制

来源:互联网 发布:json.parsearray 用法 编辑:程序博客网 时间:2024/06/05 12:20

参考:http://blog.csdn.net/hackbuteer1/article/details/7883531

可以结合博文关于C++虚继承和虚函数继承的几个例子

C++中虚函数的作用主要用于实现多态,关于多态,简而言之就是用父类指针指向其子类对象,然后通过父类的指针调用实际子类的成员函数。这种技术让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不便的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术。要么做到在编译时决议,要么试图做到运行时决议。

虚函数的工作原理

虚函数的实现要求对象携带额外的信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数(动态绑定)。典型情况下,这一信息具有一种被称为vptr(virtual table pointer,虚函数表指针)的指针的形式。vptr指向一个被称为vtbl(virtual table,虚函数表)的指针数组,每一个包含虚函数的类都关联到vtabl,然后在vtbl中寻找合适的函数指针。

如果类定义了虚函数,该类及其派生类就要生成一张虚拟函数表,即vtable。而在类的对象地址空间中存储一个该虚表的入口,占4个字节,这个入口地址是在构造对象时由编译器写入的。所以,由于对象的内存空间包含了虚表入口,编译器能够由这个入口找到恰当的虚函数,这个函数的地址不再由数据类型决定了。故对于一个父类的对象指针,调用虚拟函数,如果给他赋父类对象的指针,那么他就调用父类中的函数,如果给他赋子类对象的指针,他就调用子类中的函数(取决于对象的内存地址)。

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

通过基类指针进行虚函数调用时(也就是多态调用时),编译器静态地插入取得这个VPTR,并在BTABLE表中查找函数地址的代码,这样就能调用正确的函数完成晚绑定。为每个类设置VTABLE、初始化VPTR、为虚函数调用插入代码,所有这些都是自动发生的,我们不必关心。

虚函数的缺点

虚函数最主要的缺点是执行效率低,虚拟函数引发的多态性的实现过程比较复杂,另外就是由于要携带额外的信息(VPTR),导致类占用的内存空间比较大,对象也是一样。

含有虚函数的对象在内存布局

class A
{
private:
int a;
int b;
public:
virtual void fun0()
{
cout<<"A::fun0"<<endl;
}
};


1、直接继承

class base
{
private:
int a; 

public:
void bfun()
{
}

virtual void vfun1()
{
}

virtual void vfun2()
{
}
};

基类和派生类的VPTR指向的虚函数表(VTABLE)分别如下:

base类:
                     ——————
VPTR——>    |&base::vfun1 |
                      ——————
                      |&base::vfun2 |
                      ——————
      
derived类:
                      ———————
VPTR——>    |&derived::vfun1 |
                     ———————
                       |&base::vfun2     |
                     ———————
                       |&derived::vfun3 |
                     ———————

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

class B1

{

    int a;

    int b;

};

class B2

{

    virtual void foo(void);

    int c;

    int d;

};

 

class Test : virtual public B1, virtual public B2

{

    virtual void func1(void);

    virtual void func2(void);

    virtual void func3(void);

    int X;

};

总结一下VPTR 和 VTABLE 和类对象的关系:
       每一个具有虚函数的类都有一个虚函数表VTABLE,里面按在类中声明的虚函数的顺序存放着虚函数的地址,这个虚函数表VTABLE是这个类的所以对象所共有的,也就是说无论用户声明了多少个类对象,但是这个VTABLE虚函数表只有一个。
       在每个具有虚函数的类的对象里面都有一个VPTR虚函数指针,这个指针指向VTABLE的首地址,每个类的对象都有这么一种指针。

原创粉丝点击