多态类的vptr(指向虚函数表)何时被初始化?

9

这不是关于“VTABLE何时创建?”的问题。相反,VPTR应该在什么时候初始化?是在构造函数的开始/结束之前/之后?

A::A () : i(0), j(0)  -->> here ?
{
  -->> here ?
  //...
  -->> here ?
}
3个回答

19
虚函数调用的机制(通常是一个虚表,但不一定)在基类子对象构造之后、成员构造之前的 ctor-initializer 期间进行设置。 [class.base.init] 节规定:
“可以为正在构造的对象调用成员函数(包括虚成员函数10.3)。同样,在 ctor-initializer 中执行 typeid 操作符(5.2.8)或 dynamic_cast (5.2.7)操作也是如此。但是,如果在 ctor-initializer (或在直接或间接从 ctor-initializer 调用的函数中)执行这些操作时,还没有完成所有基类的 mem-initializers ,则操作的结果未定义。”
实际上,在构建基类子对象期间,虚函数机制已经存在,但是它是为基类设置的。 [class.cdtor]节中提到:
“在构造函数或析构函数(12.6.2)中可以调用成员函数,包括虚函数(10.3)。当从构造函数或析构函数直接或间接调用虚函数,包括在构造或销毁类的非静态数据成员期间,并且应用调用的对象是正在构造或销毁的对象(称其为x),则被调用的函数是构造函数或析构函数中的最终覆盖者,而不是在更派生类中覆盖它的函数。如果虚函数调用使用显式类成员访问(5.2.5)并且对象表达式引用x或该对象的一个基类子对象的完整对象,但不是x或其基类子对象之一,则行为未定义。”

这是否意味着,如果有多层派生,每次构造基类子对象时都会修改vptr - fengqi
@fengqi 是的,在一般情况下,各种vptr(至少每个直接多态基类一个)都会被修改。但是,在特定情况下,如果没有从ctor调用到一个具有访问权限的单独编译函数,那么在基类ctor中对x this的虚拟性进行完全内联,其中“对Info的完全内联”表示对所有可能访问Info的直接和间接调用函数进行内联,而“x的虚拟性”是指所有依赖于x的动态类型的特征或行为(特别是虚函数调用),... - curiousguy
“the complete inlining on the virtual-ness of x this of base class ctors” 意味着递归地内联所有从基类构造函数调用的函数,这些函数可能在 this 上调用虚函数(或使用 typeid(*this)dynamic_cast),从而带来了对 vptr 更改的可能优化。特别是当基类构造函数具有空体和没有非内联构造的成员对象时,它完全可以递归地内联:这种基类构造函数永远不会看到 vptr 值。 - curiousguy
(请注意,如果基类子对象B的任何数据成员M具有单独编译的构造,则无法进行转换,并且该基类子对象具有另一个具有单独编译构造的基类BB,因为由BB的基本构造函数提供给M的构造函数的this指针可能在成员对象初始化期间使用动态操作。) - curiousguy

3

它在基类和派生类的构造函数之间进行初始化:

class Base { Base() { } virtual void f(); };
class Derived { Derived(); virtual void f(); };

当原始内存转换为Base对象时会发生这种情况。 在对象构造期间将Base对象转换为Derived对象时也会发生这种情况。 当销毁对象时,同样会反向发生。即每次类型更改时,虚表指针都会改变。(我确定有人会评论说根据std,虚表不需要存在。)


0

这篇 MSDN 文章详细解释了它

文章中写道:

"最终答案是...正如你所预期的那样。它发生在构造函数中。"

所以...
A::A() : i(0), j(0)
{
-->> 在这里!
//...
//
}

但要小心,假设你有一个类 A 和一个从 A 派生的类 A1。

  • 如果你创建一个新的 A 对象,vptr 将在 A 类的构造函数开始时设置
  • 但如果你创建一个新的 A1 对象:
以下是构造A1类实例时的完整事件序列:
  1. A1::A1调用A::A
  2. A::A将vtable设置为A的vtable
  3. A::A执行并返回
  4. A1::A1将vtable设置为A1的vtable
  5. A1::A1执行并返回

1
我在回答中给出的第一个标准引用表明,您的解释并不完全正确。当添加晚回答时,最好阅读和理解现有的回答,以防万一。 - Ben Voigt
1
“here!”是错误的。我不认为“它发生在构造函数中”有帮助。在构造函数中,具体是在哪里?在成员初始化之前?在成员初始化之后?在函数体的开头?在函数体的结尾?答案是,在右大括号之后。因为对于派生类构造函数,如果您在右大括号之前调用虚函数,则仍会获得基类服务,而不是派生类服务。任何虚表调整都必须在构造函数体运行之后进行。 - SJHowe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接