If the virtual function table is the same for all objects of the class, then why can't the pointer to that table (vfptr) be static and be shared across all the objects?
问题:
回答1:
The vtable is essentially static. But you need a vptr member actually inside the object to do virtual dispatch and other RTTI operations.
On a vptr implementation, this C++ code:
class Base {
public:
virtual void f();
};
class Derived : public Base {
public:
virtual void f();
};
may act similarly to something like this:
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 };
回答2:
The virtual function table [assuming this is how the C++ compiler implements dynamic dispatch] is shared across all objects of a class. However, each object needs to know which virtual function table is relevant for this object. This is what the "virtual function table pointer" points to.
The basic idea is that the static type of a reference or a pointer to an object tells the compiler how a part of the virtual function table looks like. When it needs to do a virtual dispatch, it just follows this pointer and decides what function to call. Assume you have a base class B
and derived classes D1
and D2
like this:
#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"; }
};
The virtual function table for D1
and D2
would contain a suitable pointer to D1::f()
and D2::f()
respectively. When the compiler sees a call through a pointer or reference to B
to f()
it needs to decide at run-time which function to call:
void g(B* base) {
base->f();
}
To resolve the call it looks at where there virtual function pointer points and calls the function n the appropriate slot (more or less; the contents of the virtual function table tend to be thunks which may do any necessary pointer adjustment, too).
回答3:
"Virtual" means "determined at runtime". "Static" means "determined at translation time".
In order to make decisions at runtime, you have to have a parameter (such as the vptr) whose value can be set dynamically, at runtime. That is, for a given base object reference x
, we need some value "x.vptr
" that contains dynamic information (namely information about the most-derived class of which x
is a base subobject).
回答4:
class A
{
public:
virtual void Test();
...
};
class B: public A
{
public:
virtual void Test();
...
}
if vfptr is static for all objects, when compiling the below code:
void DoTest(A* pA)
{
...
}
A* pA = new B;
DoTest(pA);
A::vfptr will be recognized and used by compiler, but it is unexpected!