如果该类的所有对象的虚函数表都相同,那么为什么指向该表(vfptr)的指针不能是静态的并且不能在所有对象之间共享?
4 回答
vtable 本质上是静态的。但是您实际上需要在对象内部有一个 vptr 成员来执行虚拟调度和其他 RTTI 操作。
在 vptr 实现中,此 C++ 代码:
class Base {
public:
virtual void f();
};
class Derived : public Base {
public:
virtual void f();
};
可能类似于这样的行为:
class Base {
public:
Base::Base() : m_vptr(&Base_vtab) {}
Base::Base(const Base& b) : m_vptr(&Base_vtab) {}
void f() { (m_vptr->f_impl)(this); }
protected:
struct VTable {
void (*f_impl)(Base* self);
};
const VTable* m_vptr;
static const VTable Base_vtab;
private:
static void Base_f(Base* self);
};
const Base::VTable Base::Base_vtab = { &Base::Base_f };
class Derived : public Base {
public:
Derived::Derived() : Base() { m_vtpr = &Derived_vtab; }
Derived::Derived(const Derived& d) : Base(d) { m_vptr = &Derived_vtab; }
Derived::~Derived() { m_vptr = &Base::Base_vtab; }
protected:
static const VTable Derived_vtab;
private:
static void Derived_f(Derived* self);
static void Derived_f(Base* self) { Derived_f(static_cast<Derived*>(self)); }
};
const Base::VTable Derived::Derived_vtab = { &Derived::Derived_f };
虚函数表 [假设这是 C++ 编译器实现动态调度的方式] 在类的所有对象之间共享。但是,每个对象都需要知道哪个虚函数表与该对象相关。这就是“虚函数表指针”所指向的。
基本思想是引用的静态类型或指向对象的指针告诉编译器虚函数表的一部分是什么样子。当它需要进行虚拟调度时,它只是跟随这个指针并决定调用什么函数。假设您有一个基类B
和派生类D1
,D2
如下所示:
#include <iostream>
struct B {
virtual ~B() {}
virtual void f() = 0;
};
struct D1: public B {
void f() override { std::cout << "D1::f()\n"; }
};
struct D2: public B {
void f() override { std::cout << "D2::f()\n"; }
};
和的虚函数表将分别包含指向D1
和D2
的合适指针。当编译器通过指针或引用看到调用时,它需要在运行时决定调用哪个函数:D1::f()
D2::f()
B
f()
void g(B* base) {
base->f();
}
为了解决调用,它查看虚函数指针指向的位置并在适当的插槽中调用函数(或多或少;虚函数表的内容往往是 thunk,也可以进行任何必要的指针调整)。
“虚拟”的意思是“在运行时确定”。“静态”是指“在翻译时确定”。
为了在运行时做出决策,您必须有一个参数(例如 vptr),其值可以在运行时动态设置。也就是说,对于给定的基础对象引用x
,我们需要一些x.vptr
包含动态信息的值“”(即关于其最派生类x
是基础子对象的信息)。
class A
{
public:
virtual void Test();
...
};
class B: public A
{
public:
virtual void Test();
...
}
如果 vfptr 对于所有对象都是静态的,则在编译以下代码时:
void DoTest(A* pA)
{
...
}
A* pA = new B;
DoTest(pA);
A::vfptr 会被编译器识别和使用,但这是出乎意料的!