什么时候为对象设置虚拟表指针(在C ++中)?

时间:2011-10-28 20:28:24

标签: c++ virtual vtable vptr

我知道对于任何具有虚函数的类或者从具有虚函数的类派生的类,编译器会做两件事。首先,它为该类创建一个虚拟表,然后,它将虚拟指针(vptr)放在该对象的基础部分中。在运行时,此vptr被分配,并在对象实例化时开始指向正确的vtable。

我的问题是,在实例化过程中,这个vptr的确切位置是什么?这个vptr赋值是否发生在构造函数之前/之后的对象的构造函数中?

5 个答案:

答案 0 :(得分:9)

这严格依赖于实施。

对于大多数编译器,

编译器在每个构造函数的Member Initializer列表中初始化this-> __ vptr。

这个想法是让每个对象的v指针指向其类的v-table,编译器为此生成隐藏代码并将其添加到构造函数代码中。类似的东西:

Base::Base(...arbitrary params...)
   : __vptr(&Base::__vtable[0])  ← supplied by the compiler, hidden from the programmer
 {

 }

This C ++常见问题解答解释了究竟发生了什么。

答案 1 :(得分:8)

指向vtable的指针在进入层次结构中的每个构造函数时更新,然后在每个析构函数的入口处再次更新。 vptr将开始指向基类,然后在初始化不同级别时更新。

虽然你会从许多不同的人那里读到这是实现定义的,因为它是vtable的全部选择,但事实是所有编译器都使用vtable,一旦你选择vtable方法,标准确实要求< em>运行时对象的类型是正在执行的构造函数/析构函数的类型,这反过来意味着无论动态调度机制是什么,它都必须在遍历构造/销毁链时进行调整。 / p>

请考虑以下代码段:

#include <iostream>

struct base;
void callback( base const & b );
struct base {
   base() { callback( *this ); }
   ~base() { callback( *this ); }
   virtual void f() const { std::cout << "base" << std::endl; }
};
struct derived : base {
   derived() { callback( *this ); }
   ~derived() { callback( *this ); }
   virtual void f() const { std::cout << "derived" << std::endl; }
};
void callback( base const & b ) {
   b.f();
}
int main() {
   derived d;
}

标准要求该计划的输出为basederivedderivedbase,但callback中的通话是相同的所有这四个函数调用。实现它的唯一方法是在构造/销毁过程中更新对象中的vptr。

答案 2 :(得分:1)

This msdn article explains it in great detali

它说:

  

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

如果我可以在构造函数的开头添加,在构造函数中可能有的任何其他代码之前执行。


但要小心,假设您有A类,而A类来自A。

  • 如果您创建一个新的A对象,则vptr将设置在A类构造函数的开头
  • 但是如果你创建一个新对象A1:
  

“这是构建A1类实例时的整个事件序列:

     
      
  1. A1 :: A1调用A :: A
  2.   
  3. A :: A将vtable设置为A的vtable
  4.   
  5. A :: A执行并返回
  6.   
  7. A1 :: A1将vtable设置为A1的vtable
  8.   
  9. A1 :: A1执行并返回“
  10.   

答案 3 :(得分:0)

虽然它依赖于实现,但它实际上必须在构造函数体的主体被评估之前发生,因为根据C ++规范(12.7 / 3),允许通过{{1}访问非静态类方法在构造函数体中指针...因此必须在调用construtor的主体之前设置vtable,否则通过this指针调用虚拟类方法将无法正常工作。即使this指针和vtable是两个不同的东西,C ++标准允许this指针在构造函数体中使用的事实演示了编译器必须如何实现标准的vtable - this指针的兼容使用,以便正确工作,至少从时间角度来看。如果在调用构造函数体期间或之后初始化vtable,则使用this指针在构造函数体内调用虚函数或将this指针传递给依赖于动态调度的函数是有问题的,并创造不明确的行为。

答案 4 :(得分:0)

在构造函数的主体中,可以调用虚函数,因此,如果实现使用vptr,则vptr已经设置。

请注意,ctor中调用的虚函数是 构造函数的类中定义的虚函数,而不是可能被更多派生类重写的函数。

#include <iostream>

struct A
{
    A() { foo (); }
    virtual void foo () { std::cout << "A::foo" << std::endl; }
};

struct B : public A
{
    virtual void foo () { std::cout << "B::foo" << std::endl; }
};


int
main ()
{
    B b;      // prints "A::foo"
    b.foo (); // prints "B::foo"
    return 0;
}