由派生类调用时的虚函数性能

Virtual function performance when called by derived classes?

本文关键字:函数 性能 派生 调用      更新时间:2023-10-16

在编译时从已知为派生类的类调用虚拟方法时,是否会降低性能? 下面我显式调用force_speak派生类。

法典:

#include <iostream>
#include <array>
#include <memory>
class Base
{
public:
  virtual void speak()
  {
    std::cout << "base" << std::endl;
  }
};
class Derived1 : public Base
{
public:
  void speak()
  {
    std::cout << "derived 1" << std::endl;
  }
};
template<class B>
void force_speak(std::array<std::unique_ptr<B>, 3>& arr)
{
  for (auto& b: arr)
  {
    b->speak();
  }
}
int main()
{
  std::array<std::unique_ptr<Derived1>, 3> arr = 
    {
      std::unique_ptr<Derived1>(new Derived1),
      std::unique_ptr<Derived1>(new Derived1),
      std::unique_ptr<Derived1>(new Derived1)
    };
  force_speak(arr);
  return 0;
}

在编译时从已知为派生类的类调用虚拟方法时,是否会降低性能?请参阅下面的代码。

这要看情况。大多数编译器会像这样"去虚拟化"代码:

Derived1 d;
d.speak();

对象的动态类型在调用站点是已知的,因此编译器可以避免通过 vtable 进行调用,而可以直接调用Derived1::speak()

在您的示例中,编译器需要更智能,因为force_speak中只有Derived1*指针(存储在unique_ptr对象中(,在该上下文中,不清楚指向对象的动态类型是Derived1还是更派生的类型。 编译器需要内联调用以force_speak main(动态类型已知(,或者使用有关类型的一些其他知识来允许进行虚拟化解冻。(作为附加知识的一个示例,整个程序优化可以确定程序中的任何地方都没有声明其他派生类型,因此Derived1*必须指向Derived1

使用 C++11 final 关键字可以帮助编译器对某些情况进行去虚拟化,例如,如果Derived1被标记为final则编译器知道Derived1*只能指向Derived1,而不能指向从中派生的可能覆盖speak()的其他类型

  1. 它依赖于编译器。编译器必须静态地知道Derived1:speak()是唯一的选择。这包括知道没有一些Derived2::speak()定义,其中class Derived2: public Derived1 .但是编译器可能根本不实现这样的优化。

  2. 函数模板参数可由编译器自动推导。这是C++标准的一部分。编译器知道调用站点上的参数类型,因此用户不必提供它。请注意,用户可以提供类型,而 thay 可以提供例如类型兼容但不同于实际参数的类型。

在这种特定情况下,答案是也许

如果编译器决定内联force_speak()理论上可以推断出数组中的所有指针都是Derived1实例,因此静态调用该方法。 (标准不需要此优化,因此是静态调用方法还是虚拟调用该方法取决于您的特定编译器以及编译期间可能使用的选项。

如果编译器不内联调用,那么它必须发出指令以虚拟调用该方法,因为您可以进一步派生类Derived1并再次覆盖该方法,将该类的实例存储在std::unique_ptr<Derived1>中,并将这些实例的数组传递到函数中。