C++ 中的虚函数表及虚函数执行原理详解

 更新时间:2021年03月07日 10:03:44   作者:Er_HU  
这篇文章主要介绍了C++ 中的虚函数表及虚函数执行原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

为了实现虚函数,C++ 使用了虚函数表来达到延迟绑定的目的。虚函数表在动态/延迟绑定行为中用于查询调用的函数。

尽管要描述清楚虚函数表的机制会多费点口舌,但其实其本身还是比较简单的。

首先,每个包含虚函数的类(或者继承自的类包含了虚函数)都有一个自己的虚函数表。这个表是一个在编译时确定的静态数组。虚函数表包含了指向每个虚函数的函数指针以供类对象调用。

其次,编译器还在基类中定义了一个隐藏指针,我们称为 *__vptr,*__vptr 是在类实例创建时自动设置的,以指向类的虚函数表。*__vptr 是一个真正的指针,这和 *this 指针不同,*this 指针实际是一个函数参数,使编译器来达到自引用的目的。

结果就是,每个类对象都会多分配一个指针的大小,并且 *__vptr 是被派生类继承的。

如果你不清楚这些组件是怎么配合运作的,看下面的例子:

class Base
{
public:
  virtual void function1() {};
  virtual void function2() {};
};
 
class D1: public Base
{
public:
  virtual void function1() {};
};
 
class D2: public Base
{
public:
  virtual void function2() {};
};

因为这里有 3 个类,编译器会创建 3 个虚函数表。

然后编译器会在使用了虚函数的最上层基类中定义一个隐藏指针。尽管这个过程编译器会自动处理,但我们还是通过下面的例子来说明指针添加的位置:

class Base
{
public:
  FunctionPointer *__vptr;
  virtual void function1() {};
  virtual void function2() {};
};
 
class D1: public Base
{
public:
  virtual void function1() {};
};
 
class D2: public Base
{
public:
  virtual void function2() {};
};

*__vptr 在类对象创建的时候会设置成指向类的虚函数表。例如,类型 Base 被实例化的时候,*__vptr 就指向 Base 的虚函数表。类型 D1 或者 D2 被实例化的时候,*__vptr 就指向 D1 或者 D2 的虚函数表。

现在我们来看下虚函数表是怎么创建的。因为示例中每个类仅有 2 个虚函数,所以每个虚函数表会存放两个函数指针(分别指向 function1() 和 function2())。

Base 对象的虚函数表最简单。Base 对象只能访问 Base 类型的成员,不能访问 D1 或者 D2 的函数。所以 Base 的虚函数表中的两个指针分别指向 Base::function1() 和 Base::function2()。

D1 的虚函数表稍复杂点,D1 对象能够访问 D1 以及 Base 的成员。D1 重写了 function1(),但没有重写 function2(),所以 D1 的虚函数表中的两个指针分别指向 D1::function1() 和 Base::function2()。

D2 的虚函数表同理 D1,包含了分别指向 Base::function1() 和 D2::function2() 的指针。

考虑如果创建 D1 对象时会发生什么:

int main()
{
  D1 d1;
}

因为 d1 是 D1 类型对象,d1 有它自己的 *__vptr 指向 D1 类型的虚函数表。

现在创建一个 Base 类型指针 *dPtr 指向 d1:

int main()
{
  D1 d1;
  Base *dPtr = &d1;
 
  return 0;
}

重点:

因为 dPtr 是 Base 类型指针,它只指向 d1 对象的 Base 类型部分(即,指向 d1 对象中的 Base 子对象),而 *__vptr 也在 Base 类型部分。所以 dPtr 可以访问 Base 类型部分中的 *__vptr。同时,这里注意,dPtr->__vptr 指向的是 D1 的虚拟函数表,这是在 d1 初始化时就确定的。所以结果,尽管 dPtr 是 Base 类型指针,但它能够访问 D1 的虚函数表。

因此,当有调用 dPtr->function1() 时,发生了什么?

int main()
{
  D1 d1;
  Base *dPtr = &d1;
  dPtr->function1();
 
  return 0;
}

首先,程序识别到 function1() 是一个虚函数。

其次,程序使用 dPtr->__vptr 获取到了 D1 的虚函数表。

然后,它在 D1 的虚函数表中寻找可以调用的 function1() 版本,这里是 D1::function1()。

因此,dPtr->function1() 实际调用了 D1::function1()。

通过虚函数表,编译器和程序能够确定调用什么版本的虚函数,尽管使用的是指向/引用基类的指针或者引用。

调用虚函数会比调用非虚函数更慢,有以下几个原因:

  • 必须使用 *__vptr 获取正确的虚函数。
  • 必须建立虚函数表的索引来获取想要调用的函数。
  • 调用找到的函数。

结果就是必须进行三次操作才能完成对函数的调用。但是对于现代计算机系统,这些额外操作增加的时间几乎可以忽略不计。

另外,每个使用虚函数表的类都有 *__vptr 指针,从而每个类对象都会多一个指针的空间。虚函数很强大,但是它确实产生了性能开销。

到此这篇关于C++ 中的虚函数表及虚函数执行原理详解的文章就介绍到这了,更多相关C++ 虚函数表内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++动态加载so/dll库的实现

    C++动态加载so/dll库的实现

    本文主要介绍了C++动态加载so/dll库的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • 详解C++编程中类模板的相关使用知识

    详解C++编程中类模板的相关使用知识

    这篇文章主要介绍了详解C++编程中类模板的相关使用知识,包括函数的参数类型替换等方法,需要的朋友可以参考下
    2015-09-09
  • C语言中scanf的用法举例

    C语言中scanf的用法举例

    本节介绍输入函数 scanf 的用法,scanf 和 printf 一样,非常重要,而且用得非常多,所以一定要掌握,这篇文章主要介绍了C++中scanf的用法举例,需要的朋友可以参考下
    2022-11-11
  • C/C++ memset方法的误区

    C/C++ memset方法的误区

    memset 作为对内存初始化的函数,还是有不少坑和误区的,今天就来对这个函数作一个总结。避免后期使用不当踩入坑,需要的朋友可以参考下
    2021-04-04
  • C++实现获取邮件中的附件

    C++实现获取邮件中的附件

    这篇文章主要为大家详细介绍了如何通过C++实现获取邮件文件中的附件,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-01-01
  • C语言实现一个简单的扫雷游戏

    C语言实现一个简单的扫雷游戏

    扫雷是电脑上很经典的游戏,特意去网上玩了一会,几次调试之后,发现这个比三子棋要复杂一些,尤其是空白展开算法上和堵截玩家有的一拼,与实际游戏差别较大,不能使用光标,下面来详解每一步分析
    2021-10-10
  • 纯C语言:贪心Prim算法生成树问题源码分享

    纯C语言:贪心Prim算法生成树问题源码分享

    这篇文章主要介绍了贪心Prim算法生成树问题源码,有需要的朋友可以参考一下
    2014-01-01
  • C++ 中CloseHandle 函数--关闭一个句柄

    C++ 中CloseHandle 函数--关闭一个句柄

    这篇文章主要介绍了C++ 中CloseHandle 函数--关闭一个句柄的相关资料,需要的朋友可以参考下
    2017-05-05
  • C语言详解链式队列与循环队列的实现

    C语言详解链式队列与循环队列的实现

    队列(Queue)与栈一样,是一种线性存储结构,它具有如下特点:队列中的数据元素遵循“先进先出”(First In First Out)的原则,简称FIFO结构。在队尾添加元素,在队头删除元素,本篇来讲解链式队列与循环队列的实现
    2022-04-04
  • C++中数组作为函数参数传入的几种方式代码示例

    C++中数组作为函数参数传入的几种方式代码示例

    数组元素和数组名都可以作为函数的参数以实现函数间数据的传递和共享,下面这篇文章主要给大家介绍了关于C++中数组作为函数参数传入的几种方式,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-06-06

最新评论