C++ 当静态调用虚拟函数时?

C++ 当静态调用虚拟函数时?,c++,performance,pointers,polymorphism,virtual-functions,C++,Performance,Pointers,Polymorphism,Virtual Functions,直接从派生类指针调用虚函数与从基类指针调用同一派生类之间的性能差异是什么 在派生指针的情况下,调用是动态绑定还是动态绑定?我认为它是动态绑定的,因为不能保证派生指针实际上没有指向另一个派生类。如果直接通过值(而不是通过指针或引用)获得派生类,情况会发生变化吗?因此,3个案例: 指向派生对象的基指针 派生指针指向派生 按值导出 我担心性能,因为代码将在微控制器上运行 演示代码 struct Base { // virtual destructor left out for brevity

直接从派生类指针调用虚函数与从基类指针调用同一派生类之间的性能差异是什么

在派生指针的情况下,调用是动态绑定还是动态绑定?我认为它是动态绑定的,因为不能保证派生指针实际上没有指向另一个派生类。如果直接通过值(而不是通过指针或引用)获得派生类,情况会发生变化吗?因此,3个案例:

  • 指向派生对象的基指针
  • 派生指针指向派生
  • 按值导出
  • 我担心性能,因为代码将在微控制器上运行

    演示代码

    struct Base {
        // virtual destructor left out for brevity
        virtual void method() = 0;
    };
    
    struct Derived : public Base {
        // implementation here
        void method() {
        }
    }
    
    // ... in source file
    // call virtual method from base class pointer, guaranteed vtable lookup
    Base* base = new Derived;
    base->method();
    
    // call virtual method from derived class pointer, any difference?
    Derived* derived = new Derived;
    derived->method();
    
    // call virtual method from derived class value
    Derived derivedValue;
    derived.method();
    
    在运行时


    但是:性能相比于什么?将虚拟函数调用与非虚拟函数调用进行比较是无效的。您需要将其与非虚拟函数调用加上
    if
    开关
    、间接寻址或其他提供相同函数的方法进行比较。如果函数没有体现在实现之间的选择,即不需要是虚拟的,则不要使其成为虚拟的。

    前两种情况之间应该没有区别,因为虚拟函数的基本思想是始终调用实际的实现。撇开编译器优化不谈(如果在同一个编译单元中构造对象,理论上可以优化所有虚拟函数调用,并且在这两者之间无法更改指针),第二个调用也必须作为间接(虚拟)调用来实现,因为可能会有第三个类继承派生函数并实现该函数。我假设第三个调用不是虚拟的,因为编译器在编译时已经知道实际的类型。实际上,您可以通过不将函数定义为virtual来确保这一点,如果您知道您总是直接对派生类进行调用的话

    对于在小型微控制器上运行的真正轻量级代码,我建议避免将函数定义为虚拟函数。通常不需要运行时抽象。如果您编写了一个库,并且需要某种抽象,那么您可以使用模板来代替(这会给您一些编译时抽象)


    至少在PC CPU上,我经常发现虚拟呼叫是最昂贵的间接方式之一(可能是因为分支预测更困难)。有时还可以将间接性转换为数据级,例如,保留一个通用函数,该函数对不同的数据进行操作,这些数据通过指向实际实现的指针进行间接性。当然,这只适用于某些非常特殊的情况。

    必须编译虚拟函数,使其工作起来,就像它们总是被虚拟调用一样。如果您的编译器将虚拟调用编译为静态调用,则这是一个必须满足此“如同”规则的优化

    因此,编译器必须能够证明所讨论对象的确切类型。有一些有效的方法可以做到这一点:

    • 如果编译器看到对象(新的表达式或获取地址的自动变量)的创建,并且能够证明该创建实际上是当前指针值的源,那么它将获得所需的精确动态类型。你所有的例子都属于这一类

    • 当构造函数运行时,对象的类型正是包含正在运行的构造函数的类。因此,构造函数中的任何虚拟函数调用都可以静态解析

    • 同样,当析构函数运行时,对象的类型正是包含运行析构函数的类。同样,任何虚拟函数调用都可以静态解析

    好的,这些都是允许编译器将动态分派转换为静态调用的情况

    不过,所有这些都是优化,编译器可能会决定执行运行时vtable查找。但是好的优化编译器应该能够检测所有三种情况。

      理论上,唯一的区别在于C++的语法是使用限定成员名的成员函数调用。就你的类定义而言

      derived->Derived::method();
      
      此调用忽略对象的动态类型,并直接转到
      Derived::method()
      ,即静态绑定。这仅适用于调用在类本身或其一个祖先类中声明的方法

      其他一切都是一个常规的虚拟函数调用,它根据调用中使用的对象的动态类型进行解析,即动态绑定

    • 实际上,编译器将努力优化代码,并在编译时对象的动态类型已知的上下文中用静态绑定调用替换动态绑定调用。比如说

      Derived derivedValue;
      derivedValue.method();
      
      通常会在几乎所有现代编译器中生成静态绑定调用,即使语言规范没有对这种情况提供任何特殊处理

      此外,直接从构造函数和析构函数进行的虚拟方法调用通常编译为静态绑定调用

      当然,智能编译器可能能够在更广泛的上下文中静态绑定调用。例如,两者

      Base* base = new Derived;
      base->method();
      

      编译器可以将其视为简单的情况,很容易允许静态绑定调用


    您有选择吗?如果可以使用值,为什么要使用指针和虚拟函数?如果您需要一个多态调用,那么您必须使用一个。“保证vtable查找”-不,任何时候静态地知道类型,它都可以被反虚拟化,事实上,这种情况是由Clang和GCC反虚拟化的。@Basilevs这取决于编译器所做的许多优化,但在上面
    Derived* derived = new Derived;
    derived->method();