【问题标题】:When does the vptr (pointing to vtable) get initialized for a polymorphic class?什么时候为多态类初始化 vptr(指向 vtable)?
【发布时间】:2011-09-29 07:54:49
【问题描述】:

这与"When VTABLE is created?" 无关。相反,应该在什么时候初始化 VPTR?是在构造函数的开头/结尾还是构造函数之前/之后?

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

【问题讨论】:

    标签: c++ constructor virtual-functions overriding vptr


    【解决方案1】:

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

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

    当原始内存转换为 Base 对象时会发生这种情况。 在构建对象期间将 Base 对象转换为 Derived 对象时会发生这种情况。 当破坏物体时,同样明显地发生相反的情况。 IE。每次类型改变时,vtable 指针变了。 (我敢肯定,根据标准,vtables 不需要存在。)

    【讨论】:

      【解决方案2】:

      虚拟调用的机制(通常是一个 v-table,但不是必须的)在 ctor-initializer 期间设置,在基子对象构造之后和成员构造之前。部分[class.base.init]法令:

      可以为正在构建的对象调用成员函数(包括虚拟成员函数,10.3)。同样,正在构建的对象可以是 typeid 运算符 (5.2.8) 或 dynamic_cast (5.2.7) 的操作数。但是,如果这些操作在所有 mem-initializers 之前在 ctor-initializer 中执行(或在从 ctor-initializer 直接或间接调用的函数中) 对于基类已经完成,操作的结果是未定义的。

      实际上,在基类子对象的构建过程中,虚函数机制是存在的,只不过是为基类设置的。 [class.cdtor] 部分说:

      成员函数,包括虚函数 (10.3),可以在构造或销毁 (12.6.2) 期间调用。当从构造函数或析构函数直接或间接调用虚函数时,包括在类的非静态数据成员的构造或销毁过程中,调用应用的对象是对象(称为x)在构造或销毁中,调用的函数是最终的覆盖器 在构造函数或析构函数的类中,而不是在派生更多的类中覆盖它。如果虚函数调用使用显式类成员访问 (5.2.5) 并且对象表达式引用 x 的完整对象或该对象的基类子对象之一,但不是 x 或其基类子对象之一,行为未定义。

      【讨论】:

      • 这是否意味着如果派生层数很多,每次构造基础子对象时都会修改vptr
      • @fengqi 是的,在一般情况下,各种 vptr(每个直接多态碱基至少一个)被修改。 但是在特定情况下,没有从 ctor 调用可以访问 this 的单独编译函数,基类 ctor 的 x this 的虚拟性的完整内联,其中“完全内联 Info”是指内联所有可能访问 Info 的直接和间接调用的函数,而“x 的虚拟性”是所有特征或取决于 x 的动态类型(尤其是虚函数调用)的行为,...
      • ... 所以“对基类 ctor 的 x this 的虚拟性的完整内联”意味着从基类 ctor 调用的所有函数的递归内联,这些函数可能会调用一个虚函数this(或使用typeid(*this)dynamic_cast),为vptr的变化带来可能的优化。这是特别的。基类 ctor 的主体为空且没有非内联构造的成员对象的情况,这是完全递归内联的特殊情况:此类基类构造函数永远不会看到 vptr 值。
      • ...(请注意,如果该基础子对象 B 的任何数据成员 M 具有单独编译的构造并且该基础子对象具有另一个具有单独编译构造的基类 BB,则无法进行转换,如BB的基构造函数提供给M的构造函数的this指针可能会在成员对象的初始化过程中使用动态操作。)
      【解决方案3】:

      This msdn article explains it in great detali

      上面写着:

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

      所以..
      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 执行并返回 "

      【讨论】:

      • 我在回答中给出的第一个标准报价表明您的解释不太正确。添加迟到的答案时,最好阅读并理解现有答案,以防万一。
      • “这里!”是错的。我认为“它发生在构造函数中”没有帮助。 SPECIFICALLY 在构造函数中的什么位置?在成员初始化之前?成员初始化后?在身体的开始?在身体的尽头?答案是,在右花括号之后。因为对于 Derived 构造函数,如果您在右花括号之前调用虚函数,您仍然会获得 Base 类服务,而不是 Derived。任何虚拟表调整都必须在构造函数主体运行后进行。
      猜你喜欢
      • 2012-03-10
      • 2011-03-30
      • 1970-01-01
      • 1970-01-01
      • 2012-02-01
      相关资源
      最近更新 更多