虚表指针(C++中)究竟是什么时候为一个对象设置的?

4smxwvx5  于 2023-02-26  发布在  其他
关注(0)|答案(5)|浏览(188)

我知道,对于任何一个有虚函数的类,或者一个从有虚函数的类派生的类,编译器会做两件事:第一,为该类创建一个虚表;第二,在对象的基类部分放置一个虚指针(vptr);在运行时,这个vptr被赋值,并在对象示例化时开始指向正确的vtable。
我的问题是,在示例化过程中,这个vptr是在哪里被设置的?这个vptr的赋值是在构造函数之前还是之后的对象的构造函数内部发生的?

zdwk9cvp

zdwk9cvp1#

这完全取决于实施。
对于大多数编译器,
编译器在每个构造函数的成员初始化器列表中初始化this->__vptr
其思想是让每个对象的v指针指向其类的v表,编译器为此生成隐藏代码并将其添加到构造函数代码中,类似于:

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

C++常见问题解答解释了实际发生的事情的要点。

flvtvl50

flvtvl502#

指向vtable的指针在进入层次结构中的每个构造函数时更新,然后在进入每个析构函数时再次更新。vptr将开始指向基类,然后随着不同级别的初始化而更新。
虽然你会从许多不同的人那里读到这是实现定义的,因为它是vtable的全部选择,但事实是所有的编译器都使用vtable,一旦你选择了vtable方法,标准就会强制要求 * 运行时对象的类型是正在执行的构造函数/析构函数的类型 *,这反过来意味着无论动态调度机制是什么,它必须随着构造/破坏链的遍历而调整。
请考虑以下代码片段:

#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。

bgibtngc

bgibtngc3#

This msdn article explains it in great detali
上面写着:
“最后的答案是......正如你所料,它发生在构造函数中。”
如果我可以添加的话,就在构造函数的开头,在构造函数中的任何其他代码执行之前。
但是要小心,假设你有一个类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执行并返回“
e4yzc0pl

e4yzc0pl4#

在构造函数的主体中,可以调用虚函数,因此,如果实现使用vptr,则vptr已经被设置。
注意,在ctor中调用的虚函数是在 that 构造函数的类中定义的,而不是可能被更派生的类覆盖的虚函数。

#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;
}
dtcbnfnu

dtcbnfnu5#

虽然它依赖于实现,但实际上它必须在构造函数本身的主体求值之前发生,因为根据C规范,这是允许的(12.7/3),通过构造函数体中的this指针访问非静态类方法......因此,必须在调用构造函数体之前设置vtable,否则通过this指针调用虚类方法将无法正常工作。尽管this指针和vtable是两个不同的东西,事实上Cstandard允许在构造函数体中使用this指针,演示了编译器必须如何实现标准的vtable-至少从计时的Angular 来看,this指针的兼容使用才能正常工作。如果在调用构造函数主体期间或之后初始化了vtable,则使用this指针来调用构造函数体内部的虚函数或将this指针传递给依赖于动态分派的函数将是有问题的产生未定义的行为。

相关问题