C++虚函数表(vtable)和虚函数指针(vfptr)

编译器会构建一张虚表( vtable ),每一个类都有自己独特的虚表。同时,在这个继承链上,编译器会为基类插入一个隐式的指针(一般是对象的首地址),指向虚表,称为__vptr。然后,子类继承父类时,会获得继承下来的__vptr,再根据自己的类的情况兼容(修改虚函数表里的值、发生偏移等。于是,当我们构建具体的类时,若是基类类型,__vptr就会指向父类的vtable,若是子类类型,__vptr就会指向子类的vtable。

不同的类型,__vptr指向的vtable不同

#include <iostream>
using namespace std;

class A {
public:
    virtual void f() {
        cout << "A::f()" << endl;
    }
    virtual void g() {
        cout << "A::g()" << endl;
    }
};

class B :public A {
public:
    virtual void f() {
        cout << "B::f()" << endl;
    }
    virtual void h() {
        cout << "B::h()" << endl;
    }
};

int main()
{
    A a;
    B b;
    return 0;
}

父类的函数为virtual时,子类继承下来的这个函数也是虚函数,这被称为覆写。以前我们一般会推荐大家在子类也为这个函数标记为virtual,提醒我们这是虚函数,而在C++11中,我们更提倡使用override来提醒我们在覆写父类的虚函数,上面代码中,子类b继承父类a的__vptr,同时根据自己类的情况做出兼容,将_vptr所指向的虚函数表(vtable)覆盖,再在虚函数表的后面添加自己的虚函数(virtual void h())。

如果是下面的情况,即子类的第二个虚函数override 了父类的第一个虚函数。

#include <iostream>
using namespace std;

class A {
public:
    virtual void g() {
        cout << "A::g()" << endl;
    }
    virtual void h() {
        cout << "A::h()" << endl;
    }
};

class B :public A {
public:
    virtual void f() {
        cout << "B::f()" << endl;
    }
    virtual void g() {
        cout << "B::g()" << endl;
    }
};

int main()
{
    A a;
    B b;
    return 0;
}

则,会先获得指针的偏移量以保证vptr的正确性。

#include <iostream>

using namespace std;

class Test
{
public:
    Test(int a) { data = a; }
    virtual ~Test() { cout << "Test deconstruct" << endl; }//基类中的虚析构函数
    virtual void fun11() { cout << "Test virtual fun11" << endl; }//基类中的虚函数fun11
    virtual void fun12() { cout << "Test virtual fun12" << endl; }//基类中的虚函数fun12
    int data;
};


class Test1 :public Test
{
public:
    Test1(int d1, int d2) :Test(d2) { data1 = d1; data2 = d2; }
    int data1;
    int data2;
    virtual ~Test1() { cout << "test1 deconstruct" << endl; }//派生类中的虚析构函数
    virtual void fun1() { cout << "test1 virtual fun1" << endl; }//派生类中的虚函数fun1,不是实现基类中的fun11的多态
    virtual void fun2() { cout << "test1 virtual fun2" << endl; }//派生类中的虚函数fun2,不是实现基类中的fun12的多态
};

typedef void(*Fun)(void);//指向函数的指针


int main()
{
    Test objt(10);  //父类对象
    Test1 obj(1, 2);//定义子类对象obj
    cout << "obj's Size = " << sizeof(obj) << endl;
    cout << "obj 's Address = " << &obj << endl;
    cout << "second Test1 object's address = " << &obj + 1 << endl;//为了测试(int*)(&obj+1)和((int*)&obj+1)的区别
    cout << "third Test1 object's address = " << &obj + 2 << endl;
    cout << "base data address and data:        " << (int*)&obj + 1 << "\t" << *((int*)&obj + 1) << endl;
    cout << "derivate data1 address and data1:" << (int*)&obj + 2 << "\t" << *((int*)&obj + 2) << endl;
    cout << "derivate data2 address and data2:" << (int*)&obj + 3 << "\t" << *((int*)&obj + 3) << endl;

    //获得虚表指针,显示虚表中的内容
    cout << "vtable address = " << (int*)&obj << "\t" << "value = " << *((int*)&obj + 0) << endl;
    cout << "vtable value0 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 0) << endl;
    cout << "vtable value1 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 1) << endl;
    cout << "vtable value2 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 2) << endl;
    cout << "vtable value3 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 3) << endl;
    cout << "vtable value4 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 4) << endl;
    cout << "vtable value5 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 5) << endl;


    Fun pFun = NULL;

    pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 1);
    pFun();


    pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 2);
    pFun();


    pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 3);
    pFun();


    pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 4);
    pFun();
    return 0;
}

结果如下:
这里写图片描述

派生类虚表结构:

vtable
virtual ~Test()
virtual void fun11()
virtual void fun12()
virtual void fun1()
virtual void fun2()

现在更改析构函数:基类虚析构函数修改成虚构函数,子类虚析构函数
则我们有:

#include <iostream>
using namespace std;

class Test
{
public:
    Test(int a) { data = a; }
    ~Test() { cout << "Test deconstruct" << endl; }//基类中的析构函数
    virtual void fun11() { cout << "Test virtual fun11" << endl; }//基类中的虚函数fun11
    virtual void fun12() { cout << "Test virtual fun12" << endl; }//基类中的虚函数fun12
    int data;
};


class Test1 :public Test
{
public:
    Test1(int d1, int d2) :Test(d2) { data1 = d1; data2 = d2; }
    int data1;
    int data2;
    virtual ~Test1() { cout << "test1 deconstruct" << endl; }//派生类中的虚析构函数
    virtual void fun1() { cout << "test1 virtual fun1" << endl; }//派生类中的虚函数fun1,不是实现基类中的fun11的多态
    virtual void fun2() { cout << "test1 virtual fun2" << endl; }//派生类中的虚函数fun2,不是实现基类中的fun12的多态
};

typedef void(*Fun)(void);//指向函数的指针


int main()
{
    Test objt(10);
    Test1 obj(1, 2);//定义对象obj
    cout << "obj's Size = " << sizeof(obj) << endl;
    cout << "obj 's Address = " << &obj << endl;
    cout << "second Test1 object's address = " << &obj + 1 << endl;//为了测试(int*)(&obj+1)和((int*)&obj+1)的区别
    cout << "third Test1 object's address = " << &obj + 2 << endl;
    cout << "base data address and data:        " << (int*)&obj + 1 << "\t" << *((int*)&obj + 1) << endl;
    cout << "derivate data1 address and data1:" << (int*)&obj + 2 << "\t" << *((int*)&obj + 2) << endl;
    cout << "derivate data2 address and data2:" << (int*)&obj + 3 << "\t" << *((int*)&obj + 3) << endl;

    //获得虚表指针,显示虚表中的内容
    cout << "vtable address = " << (int*)&obj << "\t" << "value = " << *((int*)&obj + 0) << endl;
    cout << "vtable value0 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 0) << endl;
    cout << "vtable value1 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 1) << endl;
    cout << "vtable value2 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 2) << endl;
    cout << "vtable value3 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 3) << endl;
    cout << "vtable value4 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 4) << endl;
    cout << "vtable value5 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 5) << endl;


    Fun pFun = NULL;

    pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 0);
    pFun();


    pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 1);
    pFun();


    pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 3);
    pFun();


    pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 4);
    pFun();
    return 0;
}

结果如下:
这里写图片描述
派生类虚表结构:

vtable
virtual void fun11()
virtual void fun12()
virtual ~Test1()
virtual void fun1()
virtual void fun2()

更改析构函数:基类虚析构函数为虚虚构函数,子类虚析构函数改为析构函数
则我们有:

#include <iostream>
using namespace std;

class Test
{
public:
    Test(int a) { data = a; }
    virtual ~Test() { cout << "Test deconstruct" << endl; }//基类中的虚析构函数
    virtual void fun11() { cout << "Test virtual fun11" << endl; }//基类中的虚函数fun11
    virtual void fun12() { cout << "Test virtual fun12" << endl; }//基类中的虚函数fun12
    int data;
};


class Test1 :public Test
{
public:
    Test1(int d1, int d2) :Test(d2) { data1 = d1; data2 = d2; }
    int data1;
    int data2;
    ~Test1() { cout << "test1 deconstruct" << endl; }//派生类中的析构函数
    virtual void fun1() { cout << "test1 virtual fun1" << endl; }//派生类中的虚函数fun1,不是实现基类中的fun11的多态
    virtual void fun2() { cout << "test1 virtual fun2" << endl; }//派生类中的虚函数fun2,不是实现基类中的fun12的多态
};

typedef void(*Fun)(void);//指向函数的指针


int main()
{
    Test objt(10);
    Test1 obj(1, 2);//定义对象obj
    cout << "obj's Size = " << sizeof(obj) << endl;
    cout << "obj 's Address = " << &obj << endl;
    cout << "second Test1 object's address = " << &obj + 1 << endl;//为了测试(int*)(&obj+1)和((int*)&obj+1)的区别
    cout << "third Test1 object's address = " << &obj + 2 << endl;
    cout << "base data address and data:        " << (int*)&obj + 1 << "\t" << *((int*)&obj + 1) << endl;
    cout << "derivate data1 address and data1:" << (int*)&obj + 2 << "\t" << *((int*)&obj + 2) << endl;
    cout << "derivate data2 address and data2:" << (int*)&obj + 3 << "\t" << *((int*)&obj + 3) << endl;

    //获得虚表指针,显示虚表中的内容
    cout << "vtable address = " << (int*)&obj << "\t" << "value = " << *((int*)&obj + 0) << endl;
    cout << "vtable value0 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 0) << endl;
    cout << "vtable value1 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 1) << endl;
    cout << "vtable value2 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 2) << endl;
    cout << "vtable value3 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 3) << endl;
    cout << "vtable value4 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 4) << endl;
    cout << "vtable value5 的地址 = " << ((int*)*(int*)((int*)&obj + 0) + 5) << endl;


    Fun pFun = NULL;

    pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 1);
    pFun();


    pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 2);
    pFun();


    pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 3);
    pFun();


    pFun = (Fun)*((int*)*(int*)((int*)&obj + 0) + 4);
    pFun();
    return 0;
}

这里写图片描述

派生类虚表结构:

vtable
virtual ~Test()
virtual void fun11()
virtual void fun12()
virtual void fun1()
virtual void fun2()

注意:

  • (int*)(&obj+1)和((int*)&obj+1)的区别是:前面表示的是地址按对象大小增加,后面表示的是一个对象地址按4个字节增加。即(int*)(&obj+1)的位置就是obj的下一个对象的首地址,而((int*)&obj+1)则是对象obj内vptr后面一个成员变量的地址
  • 虚表中存放的是所有虚函数的首地址,存放顺序和(继承顺序、函数的声明顺序)有关。
  • 当派生类中重载了基类中的函数时,(包括析构函数),这时候虚表中就只会有基类中的虚函数的地址,但是当调用派生类中重载的虚函数时,实现的是重载的功能。

有一个值得思考的地方是在子类虚函数中调用已经被override的父类虚函数:

virtual void v_func1()
{
    base_class::v_func1();
    cout << "This is dev_class's v_func1()" << endl;
}

我们知道,C++比C多了个作用域限定符::
所以,隐藏的不是很深,还是可以揪出来用的
全局函数,变量,类型,enum 常量 被隐藏,可以用 ::引用
名空间内 函数,变量,类型,enum 常量被隐藏,可以用 名空间名:: 引用
类作用域的函数,变量,类型,enum 常量 被隐藏,可以用 类名:: 引用
只有函数 和 函数内部的语句组作用域,名字被隐藏,无法引用

这里你会发现,其实虚函数就是是指这个函数的签名。
而 上面那个虚函数(base_class::v_func1();)在类中的实现不是,它是一个实实在在的成员函数,也就是和普通的函数一样的。
如果这个函数是虚函数,此时编译器会把其地址放在虚表中。
所以这个函数有两个入口。
第一个就是上面那样呼叫,和普通的成员函数一致。
第二个是通过引用呼叫,此时编译器会通过虚表来呼叫。
此时需要添加一个修正 this 指针的转换函数。

所以,

base_class::v_func1();

这种格式的意思是:不要去虚表找了,直接像普通成员函数一样直接调用基类虚函数。

纯虚函数的原理其实就是:纯虚函数在类的vftable表中对应的表项被赋值为0。也就是指向一个不存在的函数。由于编译器绝对不允许有调用一个不存在的函数的可能,所以该类不能生成对象。在它的派生类中,除非重写此函数,否则也不能生成对象。

纯虚函数的作用:

  • 为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
  • 在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。

也就是制定标准,统一接口。

如果一个基类中有多个纯虚函数,即基类为抽象类,那么派生类只有实现所有纯虚函数的override,子类才会是能够实例化的具体类,任何一个父类的纯虚函数没有实现override,则子类也为抽象类不能实例化。

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值