需要澄清C++虚拟呼叫的实施
Clarification Needed on C++ Virtual Call Implementation
我对虚函数有一些疑问,或者更好的是我们可以说运行时多态性。据我说,我假设它的工作方式如下,
-
将为至少具有一个虚拟成员函数的每个类创建一个虚拟表 (V-Table(。我相信这是静态表,因此它是为每个类而不是为每个对象创建的。如果我在这里错了,请纠正我。
-
此 V 表具有虚函数的地址。如果类有 4 个虚函数,则此表有 4 个条目指向相应的 4 个函数。
-
编译器将添加一个虚拟指针 (V-Ptr( 作为类的隐藏成员。此虚拟指针将指向虚拟表中的起始地址。
假设我有这样的程序,
class Base
{
virtual void F1();
virtual void F2();
virtual void F3();
virtual void F4();
}
class Der1 : public Base //Overrides only first 2 functions of Base class
{
void F1(); //Overrides Base::F1()
void F2(); //Overrides Base::F2()
}
class Der2 : public Base //Overrides remaining functions of Base class
{
void F3(); //Overrides Base::F3()
void F4(); //Overrides Base::F4()
}
int main()
{
Base* p1 = new Der1; //Believe Vtable will populated in compile time itself
Base* p2 = new Der2;
p1->F1(); //how does it call Der1::F1()
p2->F3(); //how does it call Base::F3();
}
如果V表在编译时填充,为什么要将其称为运行时多态性?请使用上面的例子解释我有多少 vtables 和 vptr 以及它是如何工作的。据我说,3 个 Vtables 将用于 Base、Der1 和 Der2 类。在 Der1 Vtable 中,它有自己的 F1(( 和 F2(( 地址,而对于 F3(( 和 F4((,地址将指向基类。此外,3 Vptr 将作为隐藏成员添加到 Base、Der1 和 Der2 类中。如果一切都在编译时决定,那么在运行时究竟会发生什么?如果我的概念有误,请纠正我。
这显然是实现定义的,但大多数实现非常相似,或多或少与您描述的路线一致。
-
这是正确的。
-
vtables 包含的不仅仅是指向函数的指针。通常有一个条目指向RTTI信息,并且通常有一些关于如何修复此指针的信息调用函数时(尽管这也可以使用蹦床(。 在虚拟基地的情况下,也可能有虚拟基的偏移量。
-
这也是正确的。 请注意,在施工期间和销毁,编译器将更改
vptr
作为动态对象的类型发生变化,并且在多个的情况下继承(有或没有虚拟基地(,会有更多比一个vptr
. (vptr
与关于类的基址,在多重继承,并非所有类都可以具有相同的基数地址。
至于你最后说的一句话:vtables在编译时填充时间,并且是静态的。 但是 vptr 是在运行时设置的,根据动态类型,函数调用使用它来找到 vtable 并调度呼叫。
在您的(非常简单的(示例中,有三个 vtable,一个用于每个班级。 因为只涉及简单的继承,所以有每个实例只有一个 VPTR,在 Base
和派生类。 Base
的 vtable 将包含四个插槽,指向Base::f1
、Base::f2
、Base::f3
和Base::f4
。用于Der1
的 vtable 还将包含四个插槽,指向 Der1::f1
、Der1::f2
、Base::f3
和Base::f4
。 视频桌因为Der2
将指向Base::f1
、Base::f2
、Der2::f3
和 Der2::f4
. Base
的构造函数会将 vptr 设置为目录 Base
;派生类的构造函数将首先调用基类的构造函数,然后设置 VPTR到与其类型对应的 vtable。 (在实践中,在这样的简单的情况,编译器可能能够确定VPTR 从不用于构造函数中以Base
,因此跳过设置它。 在更复杂的情况下,编译器看不到基类构造函数的所有行为,但是,情况并非如此。
至于为什么它被称为运行时多态性,请考虑一个函数:
void f(Base* p)
{
p->f1();
}
实际调用的函数会有所不同,具体取决于无论p
指向Der1
还是Der2
. 换句话说,它将在运行时确定。
C++标准没有指定如何实现虚函数调用,但这里有一个普遍接受的方法的简化示例。
从高级别的角度来看,v-table 如下所示:
基地:
Index | Function Address
------|------------------
0 | Base::F1
1 | Base::F2
2 | Base::F3
3 | Base::F4
Der1:
Index | Function Address
------|------------------
0 | Der1::F1
1 | Der1::F2
2 | Base::F3
3 | Base::F4
Der2:
Index | Function Address
------|------------------
0 | Base::F1
1 | Base::F2
2 | Der2::F3
3 | Der2::F4
当你创建p1
和p2
时,它们会得到一个指针,分别指向Der1
的 vtable 和 Der2
的 vtable。
对p1->F1
的调用基本上意味着"在p1
的虚拟表上调用函数0"。 vptr[0]
是Der1::F1
,所以它被称为。
之所以称为运行时多态性,是因为将为特定对象调用的函数是在运行时确定的(通过在对象的 vtable 中进行查找(。
它是实现定义的。在C++编程时,唯一应该关注的是,如果您声明方法virtual
,指针或引用后面对象的运行时内容将决定将调用哪些代码。
也许你应该先阅读这个话题。这是C++具体的东西。
我不打算介绍四个虚函数和三个派生类型。可以这么说:对于最终基类,vtable 具有指向所有虚函数的基类版本的指针。对于派生类,vtable 具有指向派生类的所有虚函数的指针;当派生类重写基类函数时,该函数的函数指针指向该虚函数的派生类版本;当派生类继承虚函数时,函数指针指向继承的函数。
- 虚拟决赛作为安全
- 呼叫运营商<<临时
- PowerPC ppc64le上的Gcc Woverloaded虚拟错误
- 如何在C++中获得"静态纯虚拟"功能?
- C++无法定义虚拟函数 OUTER 类和头文件
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 在模板基类中为继承类中的可选重写生成虚拟方法
- 尝试将unique_ptrs推送到向量时使用纯虚拟函数错误
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践
- 大小虚拟继承中的派生类
- 链接器找不到在虚拟类 c++ 中访问的静态字段的符号
- 使用 C++ 和 i2c 工具从虚拟 i2c 写入和读取
- 我是否错过了什么,或者虚拟呼叫的性能并不像人们所说的那样糟糕
- 确定LLVM中的虚拟呼叫站点
- 线程中的虚拟呼叫忽略派生类
- 删除虚拟呼叫
- 当我知道类型时,如何避免虚拟呼叫
- std::使用虚拟呼叫操作员转换抛出"global functions do not have 'this' pointers"
- 需要澄清C++虚拟呼叫的实施
- 捕获R6025纯虚拟呼叫