C++虚函数会导致性能开销大?

C++虚函数作用

C++中的虚函数的作用主要是实现了多态的机制。

虚函数是通过一张虚函数表(Virtual Table)实现的。在这个表中,主要是一个类的虚函数地址表,这张表解决了继承和覆盖等问题。就像一张地图一样,指明了指针实际所应该调用的函数。


C++虚函数特点

①我们在声明了一个有虚函数的类的对象同时,这个对象被添加了一个隐式成员。该成员保存了指向虚函数地址数组的指针。

②只要包含虚函数的类就会有一个虚函数表,当这个类是基类时,它的派生类也会有相应的虚函数表。当一个类有多个对象的时候,这些对象共享一个虚函数表。

虚函数表是编译器生成的,程序运行时被载入内存。

示意代码:

#include 
using namespace std;
class A
{
public:
    int i;
    virtual void func() {}
    virtual void func2() {}
};
class B : public A
{
    int j;
    void func() {}
};
int main()
{
    A a;
    B b;
    size_t len_a = sizeof(a);
    size_t len_b = sizeof(b);
    cout << len_a << ", " << len_b<

示意图:

类A对象的存储空间以及虚函数表


类B对象的存储空间以及虚函数表

使用VS2022编译查看的对象存储空间以及虚函数表


虚函数的函数调用机制

①正常的函数调用:

  1. 复制栈上的一些寄存器,以允许被调用的函数使用这些寄存器;
  2. 将参数复制到预定义的位置,这样被调用的函数可以找到对应参数;
  3. 入栈返回地址;
  4. 跳转到函数的代码,这是一个编译地址,因为编译器/链接器硬编码为二进制;
  5. 从预定义的位置获取返回值,并恢复想要使用的寄存器。

②虚函数调用:

虚函数调用与此完全相同,唯一的区别就是编译时不知道函数的地址,而是,

  1. 从对象中获取虚表指针,该指针指向一个函数指针数组,每个指针对应一个虚函数;
  2. 从虚表中获取正确的函数地址,放到寄存器中;
  3. 跳转到该寄存器中的地址,而不是跳转到一个硬编码的地址。


虚函数可能会造成性能损失的原因总结

从上面的分析可以看出来,虚函数可能会造成性能损失:

● 构造函数必须初始化vptr。

● 虚函数是通过指针间接调用的,所以必须先得到指向虚函数表的指针,然后再获得正确的函数偏移量。

● 内联是在编译时决定的。编译器不可能把运行时才解析的虚函数设置为内联。


总结

虚函数其实最主要的性能开销在于它阻碍了编译器内联函数和各种函数级别的优化,导致性能开销较大。如果代码中使用了更多的虚函数,编译器能优化的代码就越少,性能就越低。所以,评估虚函数的性能损失就是评估无法内联该函数所造成的损失。这种损失的代价并不固定,它取决于函数的复杂程度和调用频率。

虚函数通常通过虚函数表来实现,在虚表中存储函数指针,实际调用时需要间接访问,这需要多一点时间。然而这并不是虚函数速度慢的主要原因,真正原因是编译器在编译时通常并不知道它将要调用哪个函数,所以它不能被内联优化和其它很多优化,因此就会增加很多无意义的指令(准备寄存器、调用函数、保存状态等)。真正的问题不是虚函数,而是那些不必要的间接调用。

通常,使用虚函数没问题,它的性能开销也不大,而且虚函数在面向对象代码中有强大的作用。但是不能无脑使用虚函数,特别是在性能至关重要的或者底层代码中,而且大项目中使用多态也会导致继承层次很混乱。


有什么好方法替代虚函数呢?

  • 使用访问者模式来使类层次结构可扩展;
  • 使用普通模板替代继承和虚函数;
  • 使用variants替代虚函数或模板方法。

页面更新:2024-03-30

标签:函数   性能   寄存器   内联   编译器   开销   指针   损失   对象   代码   地址

1 2 3 4 5

上滑加载更多 ↓
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top