C++中的虚函数以及虚函数表

Posted yinbiao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++中的虚函数以及虚函数表相关的知识,希望对你有一定的参考价值。

一.虚函数的定义

被virtual关键字修饰的成员函数,目的是为了实现多态

ps:

关于多态【接口和实现分离,父类指针指向子类的实例,然后通过父类指针调用子类的成员函数,这样可以让父类指针拥有多种形态,所以称之为多态】

二.虚函数表

该表为一个类的虚函数的地址表,用于解决继承和覆盖的问题

1.拥有虚函数的类才有虚函数表

2.虚函数表属于类,然后类的所有对象通过虚函数表指针共享类的虚函数表

3.虚函数表的作用:当使用父类指针来操作子类对象时,虚函数表就像一个地图一样,指明了实际所应该调用的函数

4.c++编译器保证虚函数表的指针存在于对象实例中最前面的位置(为了保证在多层继承或者多重继承的情况下获得函数表的性能),这意味着我们可以通过对象实例的地址得到虚函数表,然后就可以遍历其中的虚函数指针,并且调用响应的虚函数

ps:多重继承:多个父类,多层继承:父类还存在父类

技术图片

【通过虚函数表,遍历虚函数指针,调用响应的虚函数】

技术图片
#include<bits/stdc++.h>
using namespace std;
class Base

public:
    virtual void f()
    
        cout << "Base::f" << endl;
    
    virtual void g()
    
        cout << "Base::g" << endl;
    
    virtual void h()
    
        cout << "Base::h" << endl;
    

;
typedef void(*Fun)(void);
Base b;
Fun pFun = NULL;

int main()

    cout << "虚函数表地址:" << (int*)(&b) << endl;
    cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;

    //通过虚函数表调用虚函数
    pFun = (Fun)*((int*)*(int*)(&b));   // Base::f()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&b)+1);  // Base::g()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&b)+2);  // Base::h()
    pFun();
View Code

结果:

技术图片
虚函数表地址:0x477008
虚函数表 — 第一个函数地址:0x473668
Base::f
Base::g
Base::h
View Code

以上为无继承情况

1.单层继承无虚函数覆盖的情况

技术图片

技术图片

1)虚函数按照声明顺序放入表中

2)父类虚函数在前,子类虚函数在后

3)末尾点号为虚函数表的结尾标识符,在不同编译器下值不同

技术图片
#include<bits/stdc++.h>
using namespace std;
class Base

public:
    virtual void f()
    
        cout << "Base::f" << endl;
    
    virtual void g()
    
        cout << "Base::g" << endl;
    
    virtual void h()
    
        cout << "Base::h" << endl;
    

;
class Base_son:public Base

public:
    virtual void f1()
    
        cout << "Base_son::f1" << endl;
    
    virtual void g1()
    
        cout << "Base_son::g1" << endl;
    
    virtual void h1()
    
        cout << "Base_son::h1" << endl;
    

;


typedef void(*Fun)(void);
Base_son d;

Fun pFun = NULL;

int main()

    cout << "虚函数表地址:" << (int*)(&d) << endl;
    cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&d) << endl;

    //通过虚函数表调用虚函数
    pFun = (Fun)*((int*)*(int*)(&d));   // Base::f()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+1);  // Base::g()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+2);  // Base::h()
    pFun();

    pFun =(Fun)*((int*)*(int*)(&d)+3);  // Base_son::f1()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+4);  // Base_son::g1()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+5);  // Base_son::h1()
    pFun();

    return 0;
View Code

结果:

技术图片
虚函数表地址:0x477008
虚函数表 — 第一个函数地址:0x473668
Base::f
Base::g
Base::h
Base_son::f1
Base_son::g1
Base_son::h1
View Code

2.单层继承有虚函数覆盖的情况

技术图片

技术图片

1)覆盖的f()函数被放到了虚函数表中原父类虚函数的位置

2)没有被覆盖的函数没有变化

技术图片
#include<bits/stdc++.h>
using namespace std;
class Base

public:
    virtual void f()
    
        cout << "Base::f" << endl;
    
    virtual void g()
    
        cout << "Base::g" << endl;
    
    virtual void h()
    
        cout << "Base::h" << endl;
    

;
class Base_son:public Base

public:
    virtual void f()
    
        cout << "Base_son::f" << endl;
    
    virtual void g1()
    
        cout << "Base_son::g1" << endl;
    
    virtual void h1()
    
        cout << "Base_son::h1" << endl;
    

;


typedef void(*Fun)(void);
Base_son d;

Fun pFun = NULL;

int main()

    cout << "虚函数表地址:" << (int*)(&d) << endl;
    cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&d) << endl;

    //通过虚函数表调用虚函数
    pFun = (Fun)*((int*)*(int*)(&d));   // Base_son::f()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+1);  // Base::g()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+2);  // Base::h()
    pFun();

    pFun =(Fun)*((int*)*(int*)(&d)+3);  // Base_son::g1()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+4);  // Base_son::h1()
    pFun();

    return 0;
View Code

结果:

技术图片
虚函数表地址:0x477008
虚函数表 — 第一个函数地址:0x473650
Base_son::f
Base::g
Base::h
Base_son::g1
Base_son::h1
View Code

通过父类指针指向子类实例,子类覆盖父类方法,然后调用子类的方法,这样就实现了多态

Base *b=new Base_son(); b->f();

3.多重继承无虚函数覆盖

技术图片

技术图片

1)每个父类都有自己的虚函数表

2)子类的虚函数被放到第一个父类的虚函数表中

这样做是为了解决不同的父类类型指针指向同一个子类实例,而能够调用到实际的函数

4.多重继承存在虚函数覆盖

技术图片

技术图片

1)父类虚函数表中被覆盖的虚函数全部被替换成了子类的覆盖虚函数

这样我们就通过父类指向子类从而访问子类的f()了

技术图片
Derive d;
Base1 *b1 = &d;
Base2 *b2 = &d;
Base3 *b3 = &d;
b1->f(); //Derive::f()
b2->f(); //Derive::f()
b3->f(); //Derive::f()
 
b1->g(); //Base1::g()
b2->g(); //Base2::g()
b3->g(); //Base3::g()
View Code

使用虚函数表可以做一些违反c++语义的事情:

1)通过父类指针访问子类自己的虚函数

子类的虚函数X在父类中没有,所以子类的虚函数X没有覆盖父类的虚函数,但是如果我们通过父类的指针来访问子类自己的虚函数的编译器会报错

Base1 *b1 = new Derive();
b1->f1();  //编译出错

但是我们通过虚函数表可以做到这种违背C++语义的事情:使用父类指针访问子类自己的虚函数

2)访问父类non-public的虚函数

如果父类的虚函数是private或protected的,但是这些feipublic的父类虚函数同样会存在于虚函数表中,所以我们可以通过访问虚函数表访问到这些虚函数

附上多重继承有虚函数覆盖的样例代码:

技术图片
#include <iostream>
using namespace std;
 
class Base1 
public:
            virtual void f()  cout << "Base1::f" << endl; 
            virtual void g()  cout << "Base1::g" << endl; 
            virtual void h()  cout << "Base1::h" << endl; 
 
;
 
class Base2 
public:
            virtual void f()  cout << "Base2::f" << endl; 
            virtual void g()  cout << "Base2::g" << endl; 
            virtual void h()  cout << "Base2::h" << endl; 
;
 
class Base3 
public:
            virtual void f()  cout << "Base3::f" << endl; 
            virtual void g()  cout << "Base3::g" << endl; 
            virtual void h()  cout << "Base3::h" << endl; 
;
 
class Derive : public Base1, public Base2, public Base3 
public:
            virtual void f()  cout << "Derive::f" << endl; 
            virtual void g1()  cout << "Derive::g1" << endl; 
;
 
typedef void(*Fun)(void);
 
int main()

            Fun pFun = NULL;
 
            Derive d;
            int** pVtab = (int**)&d;
 
            //Base1‘s vtable
            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);
            pFun = (Fun)pVtab[0][0];
            pFun();
 
            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);
            pFun = (Fun)pVtab[0][1];
            pFun();
 
            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);
            pFun = (Fun)pVtab[0][2];
            pFun();
 
            //Derive‘s vtable
            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);
            pFun = (Fun)pVtab[0][3];
            pFun();
 
            //The tail of the vtable
            pFun = (Fun)pVtab[0][4];
            cout<<pFun<<endl;
 
            //Base2‘s vtable
            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
            pFun = (Fun)pVtab[1][0];
            pFun();
 
            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
            pFun = (Fun)pVtab[1][1];
            pFun();
 
            pFun = (Fun)pVtab[1][2];
            pFun();
 
            //The tail of the vtable
            pFun = (Fun)pVtab[1][3];
            cout<<pFun<<endl;
 
            //Base3‘s vtable
            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
            pFun = (Fun)pVtab[2][0];
            pFun();
 
            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
            pFun = (Fun)pVtab[2][1];
            pFun();
 
            pFun = (Fun)pVtab[2][2];
            pFun();
 
            //The tail of the vtable
            pFun = (Fun)pVtab[2][3];
            cout<<pFun<<endl;
 
            return 0;
View Code

 

关于虚函数和普通函数:

1.类中的虚函数是动态生成的,由虚函数表的指向进行访问,不为类的对象分配内存,没有虚函数表,就无法访问虚函数

2.类中的普通函数静态生成,不为类的对象分配内存也可访问

参考:左耳朵耗子:C++虚函数表解析

 

以上是关于C++中的虚函数以及虚函数表的主要内容,如果未能解决你的问题,请参考以下文章

C++反汇编第二讲,反汇编中识别虚表指针,以及指向的虚函数地址

9-4:C++多态之单继承和多继承中的虚函数表

Java和C++的虚函数的异同

C++中的虚函数(类的向上转换,和向下转换)

深入理解C++ 虚函数表

C++中,子类会继承父类的虚函数表!对于父类的析构函数(虚函数) 也会继承吗?