虚拟关键字如何导致动态绑定?

How virtual keyword leads to dynamic binding?

提问人:dRIFT sPEED 提问时间:8/31/2023 更新时间:8/31/2023 访问量:56

问:

我知道以下几点

编译器为每个包含至少一个虚拟函数的类构造一个虚拟表。它还向基类添加了一个指针(v_ptr)(假设它具有虚拟函数),并且该指针被每个派生类继承。创建类的对象时,会发出相同的指针来指向该对象所属类的虚拟表。

现在,如果我没记错的话,以上所有内容都只发生在编译时。如果是这样,那么请考虑以下示例 virtual 关键字如何导致动态绑定。

#include <iostream>
using namespace std;

class base {
    public:
        virtual void show()
        {
            cout << "base\n";
        }
};
 class derived:public base{
    public:
        void show()
        {
            cout << "derived\n";
        }
 };


int main()
{
    base* ptr;
    derived drv1;
    ptr = &drv1;
    ptr->show();
    return(0);
}

在上面的代码中,动态绑定是如何在语句中发生的,因为我认为在编译时一切都是已知的来绑定函数。 通过它调用,指向其基础将包含指向其类的虚拟表v_ptr因此编译器仅在编译时知道要调用哪个表。ptr->show();show()ptrderv1show()

注 - 基表示从基类继承的部分。derv1

C++ 虚函数 动态绑定 运行时多态性

评论

1赞 Jesper Juhl 8/31/2023
标准没有规定如何实现虚函数,不同的编译器可以做不同的事情。基于 vtable 的实现很常见,但这并不是实现虚拟功能的唯一方式。此外,编译器可能会(在某些情况下确实会这样做)对虚拟函数调用进行去虚拟化。
0赞 user17732522 8/31/2023
"现在,如果我没记错的话,以上所有内容都只发生在编译时“:不,”当创建类的对象时,会发出相同的指针来指向该对象所属类的虚拟表“部分发生在运行时。它无法以任何其他方式工作。指针在创建时必须写入类类型的任何对象中。
0赞 user17732522 8/31/2023
"在上面的代码中 [...] 因为我认为在编译时一切都是已知的来绑定函数。[...]因此编译器只知道在编译时调用哪个 show().“:这不会改变任何事情。当然,如果编译器可以确定在编译时实际调用什么函数(“去虚拟化”),则可以将间接函数优化为直接调用,但这不会影响任何程序语义,也不需要编译器这样做。此外,vptr 和 vtable 无论如何都是实现细节。虚拟呼叫的语言规范与此无关。
0赞 Pepijn Kramer 8/31/2023
它被称为动态多态性是有原因的。在这种情况下,动态意味着运行时选择正确的实现(有时编译器可以优化到编译时,但这不是必需的)。编译时的静态多态性也存在,但这通常是通过模板完成的。
0赞 n. m. could be an AI 8/31/2023
“现在,如果我没记错的话,以上所有内容都只发生在编译时。”你错特错了。

答:

2赞 j6t 8/31/2023 #1

在这一行中

derived drv1;

创建类的实例。为此,执行类的构造函数。(在此示例中,所有构造函数都是隐式声明和定义的,但这一事实没有区别。

  1. begins 的构造函数。它立即调用 的构造函数。derivedbase
  2. 的构造函数填充实例的虚拟表指针以指向 的虚拟调度表。basebase
  3. 的构造函数结束后的 resumes 的构造函数。derivedbase
  4. 此时,它会将实例的虚拟表指针更改为指向 的虚拟调度表。derived

关键是实例的演变发生在运行时,而不是在编译时。

当然,优化可能会将事情降低到在生成的程序集中看不到此过程的任何内容的水平(“去虚拟化”)。