我的问题是,在实例化过程中到底在什么地方设置了这个vptr?这个vptr的赋值是在对象的构造函数内部还是之前/之后发生的?
这是严格依赖于实现的。
对于大多数编译器而言,
编译器在每个构造函数的成员初始化列表中初始化 this->__vptr
。
其目的是使每个对象的虚指针指向其类的虚函数表,并且编译器会生成隐藏代码并将其添加到构造函数代码中。大致如下:
Base::Base(...arbitrary params...)
: __vptr(&Base::__vtable[0]) ← supplied by the compiler, hidden from the programmer
{
}
这篇 C++ FAQ解释了虚函数的基本概念。
vtable
、vptr
机制,并且应该使用这样的机制,但由于实现不受标准约束,因此出现了第一个声明。 - Alok Save每个构造函数的入口和每个析构函数的入口都会更新指向虚函数表的指针。vptr将开始指向基类,随着不同层次的初始化而更新。
虽然您可能会从许多不同的人那里读到这是实现定义的,因为它是整个vtable选择的问题,但事实是所有编译器都使用vtables,一旦您选择了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;
}
base
, derived
, derived
, base
,但对函数的四次调用中来自callback
的调用相同。唯一实现这一点的方法是在对象的构造/析构过程中更新对象中的vptr。文章中说:
"最终的答案就是...顾名思义,它发生在构造函数中。"
如果我可以补充一下,在构造函数的开始之前,执行任何其他可能存在的构造函数代码。
但要小心,假设您有类A和从A派生的类A1。
"当您构造类A1的实例时,以下是整个事件序列:
- A1::A1调用A::A
- A::A将vtable设置为A的vtable
- A::A执行并返回
- A1::A1将vtable设置为A1的vtable
- A1::A1执行并返回"
vptr
,那么该 vptr
已经设置好了。#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;
}
A
的引用,而调度程序调用虚函数,则标准规定必须产生相同的结果,在这种情况下,编译器无法使用静态分派,因为它在处理调度程序时不知道调用来自哪里。 - David Rodríguez - dribeas虽然它是实现相关的,但实际上必须在构造函数本身的主体被评估之前发生,因为根据C++规范(12.7/3),您可以通过构造函数主体中的this
指针访问非静态类方法... 因此,在调用构造函数之前必须设置vtable,否则通过this
指针调用虚拟类方法将无法正确工作。尽管this
指针和vtable是两个不同的东西,但C++标准允许在构造函数主体中使用this
指针表明编译器必须如何实现vtable以使符合标准的this
指针使用从时间角度正确地工作。如果在构造函数主体期间或之后初始化vtable,则在构造函数主体内使用this
指针调用虚拟函数或将this
指针传递给依赖于动态分派的函数将会有问题并创建未定义的行为。
this
指针进行访问,并且vtable未设置,则在调用非静态虚拟函数时会遇到一些问题,因为虚拟函数调用是通过指向this
指针的类实例的vtable进行的。 - Jason* this
的动态类型根据定义已知 (2) 编译器已知对象的动态类型时,对虚拟调用不必使用vptr。就只有这些。你似乎认为在构造函数中有一个特殊规则与this
有关,但实际上并没有。 - curiousguy
*this
传递给一个以你的基础类型引用为参数的函数,并从那里调用虚函数。在那个函数内部进行的分配必须是动态的,因为编译器不知道调用者是谁。 - David Rodríguez - dribeas