"Direct" vs "virtual" 调用虚函数

"Direct" vs "virtual" call to a virtual function

我是自学成才的,因此很多术语都不熟悉。我似乎无法通过谷歌搜索找到答案:What is a "virtual" vs a "direct" call to a virtual function?

这属于术语,而不是技术细节。我问的是何时将呼叫定义为 "directly" 与 "virtually"。 它不涉及 vtables,也不涉及与这些概念的实现有关的任何其他内容。

假设你有这个class:

class X { 
public: 
    virtual void myfunc(); 
};  

如果为X类型的普通对象调用虚函数,编译器会生成直接调用,即直接引用X::myfunct()

X a;         // object of known type 
a.myfunc();  // will call X::myfunc() directly    

如果您通过指针解引用或引用调用虚函数,则不清楚所指向的对象真正具有哪种类型。它可以是 X,但也可以是从 X 派生的类型。然后编译器将进行虚拟调用,即使用指向函数地址的 table 指针:

X *pa;        // pointer to a polymorphic object  
...           // initialise the pointer to point to an X or a derived class from X
pa->myfunc(); // will call the myfunc() that is related to the real type of object pointed to    

这里有一个 online simulation 代码。您会看到,在第一种情况下,生成的程序集调用函数的地址,而在第二种情况下,编译器将某些内容加载到寄存器中并使用该寄存器进行间接调用(即调用的地址不是 "hard-wired" 并将在 运行 时动态确定)。

你的问题的答案在不同的概念层面是不同的。

  • 在概念语言级别,非正式术语 "virtual call" 通常指的是根据调用中使用的对象的 动态类型 解析的调用.根据 C++ 语言标准,这适用于对虚函数的所有调用,使用函数的 限定名称 的调用除外。当在调用中使用方法的限定名称时,调用称为 "direct call"

    SomeObject obj;
    SomeObject *pobj = &obj;
    SomeObject &robj = obj;
    
    obj.some_virtual_function(); // Virtual call
    pobj->some_virtual_function(); // Virtual call
    robj.some_virtual_function(); // Virtual call
    
    obj.SomeObject::some_virtual_function(); // Direct call
    pobj->SomeObject::some_virtual_function(); // Direct call
    robj.SomeObject::some_virtual_function(); // Direct call
    

    请注意,您经常可以听到人们说通过 直接对象 对虚函数的调用是 "not virtual"。然而,语言规范并不支持这种观点。根据语言,所有对虚函数的非限定调用都是相同的:它们根据对象的动态类型来解析。在那种[概念]意义上,它们都是虚拟的

  • 在实现级别,术语 "virtual call" 通常指的是通过一些实现定义的机制调度的调用,这些机制实现了虚函数的标准要求的功能。通常它是通过与调用中使用的对象关联的虚拟方法 Table (VMT) 实现的。然而,聪明的编译器只会在真正需要时才使用 VMT 来执行对虚函数的调用,即当对象的动态类型在编译时未知时。在所有其他情况下,编译器将努力直接调用该方法,即使该调用在概念级别上是正式的 "virtual"。

    例如,大多数时候,使用直接对象(而不是指针或对象引用)对虚函数的调用将作为直接调用来实现(不涉及 VMT 调度)。这同样适用于从对象的构造函数和析构函数

    立即调用虚函数
    SomeObject obj;
    SomeObject *pobj = &obj;
    SomeObject &robj = obj;
    
    obj.some_virtual_function(); // Direct call
    pobj->some_virtual_function(); // Virtual call in general case
    robj.some_virtual_function(); // Virtual call in general case
    
    obj.SomeObject::some_virtual_function(); // Direct call
    pobj->SomeObject::some_virtual_function(); // Direct call
    robj.SomeObject::some_virtual_function(); // Direct call
    

    当然,在后一种意义上,如果编译器有足够的信息,没有什么能阻止编译器将对虚函数的任何调用实现为直接调用(不涉及VMT分派)在编译时确定对象的动态类型。在上面的简单示例中,任何现代编译器都应该能够将所有调用实现为直接调用。