C++ 虚函数,虚表,虚指针的关系

关于虚表和虚指针自己并没有仔细去学习过,只是在网上博客中看别人在代码里说来说去,也没有真正搞明白是怎么回事,直到有一天在某车企面试被深挖虚表和STL,当场懵逼。今天在C++书中学习了虚表相关的知识点,故总结出来,方便查阅。

C++语法规定了虚函数的行为,把具体的实现方法交给了IDE的程序员。通常编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针,这就是虚指针这个数组称为虚函数表,也就是虚表虚表中存储了为类对象进行声明的虚函数的地址。

...
class Base
{
......
    char name[40];
public:
     virtual void show_name();
     virtual void show_all();  
......
}
class Child : public Base //Child继承自Base
{
    ......
    char field[40];
public :
    void show_all(); //对基类show_all重新定义,这里省略实现
    virtual void show_field();// new function
    ......
};

例如,基类对象包含一个指针,该指针指向基类中所有虚函数的地址表(虚表)。派生类对象将包含一个指向独立地址表的指针(虚指针)如果派生类提供了虚函数的新定义,那么该虚表将保存新函数的地址。如果派生类没有重新定义虚函数,该虚表将保存函数原始版本的地址。如果派生类定义了新的虚函数,则该函数的地址也将被添加到虚表中。如下图所示

如下图所示,Base中有一个隐藏的指针成员vptr,它指向Base的虚表,Child中有一个隐藏的指针成员vptr,它指向Child的虚表,

Child adam("Adam Cruh","nuclear struct");
Base *psc=&adam;
psc->show_all();

如下图:

在执行Base *psc=&adam; 过程中,基类的指针指向了派生类对象,通过虚函数体现了C++的多态性,对应的虚表虚指针相关步骤有:

(1)获悉psc->vptr的地址(2096)

(2)前往地址2096处的虚表

(3)获悉虚表中第2个函数的地址(6820)

(4) 前往地址6820,并执行这里的虚函数(show_all() )

 

调用虚函数时,程序将查看存储在对象中的vtbl(虚表)地址,然后转向相应的函数地址表。如果使用类声明中定义的第一个虚函数,则程序将使用虚表中的第一个函数地址,并执行具有该地址的函数。如果使用类声明中定义的第三个虚函数,则程序将使用虚表中的第三个函数地址。

由上可分析得到,由于虚指针和虚表的存在,使用虚函数在内存和执行速度方面有一定的开销。

此外,有些关于虚函数的注意事项

(1)构造函数不能是虚函数。创建派生类对象时,将先调用基类构造函数,再是派生类对象成员构造函数,最后是派生类本身的构造函数,派生类不继承基类的构造函数,所以类的构造函数为虚函数的话将没有什么意义

(2)基类的析构函数通常是虚函数。否则当出现Base *psc=new Child 的情况时,执行Child的析构函数时将只析构基类对象而不析构子类对象,造成内存泄漏。

(3)友元不能是虚函数,因为友元不是类的成员函数,只有成员函数才能是虚函数。如果因为这一原因造成了设计问题,可以让友元函数调用虚成员函数来解决。

(4)如果派生类没有重新定义,那么将使用该函数的基类版本。如果派生类在派生链中,那么将使用最新的虚函数版本

(5)如果派生类的虚函数与基类的同名虚函数的参数列表不同,那么将隐藏基类的函数,使用当前最新的派生类虚函数,这不是函数的重载,而是在重新定义方法后,对基类方法的隐藏。

(6)返回类型协变:如果基类虚函数的返回值类型为基类指针或引用,则在派生类的虚函数版本中可以把返回值类型修改为指向派生类的引用或指针。

(7)如果基类中虚函数声明被多次重载,那么应在派生类中重新定义所以的基类版本。如果只定义一个函数,则另外其他同名重载函数将被隐藏,只能调用基类的其他版本。

 

 

posted @ 2021-11-30 23:31  北陌南旬  阅读(329)  评论(2)    收藏  举报