Why can't the virtual function table pointer (vfptr) be static in C++?

跟風遠走 提交于 2019-12-04 08:11:00

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

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).

"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).

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!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!