为什么虚函数表指针(vfptr)在C ++中不能是静态的?

时间:2014-08-23 16:02:19

标签: c++ virtual-functions dynamic-dispatch

如果虚拟函数表对于类的所有对象都是相同的,那么为什么指向该表的指针(vfptr)不能是静态的并且在所有对象之间共享?

4 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:2)

虚拟函数表[假设这是C ++编译器实现动态调度的方式]在类的所有对象之间共享。但是,每个对象都需要知道哪个虚函数表与此对象相关。这就是&#34;虚函数表指针&#34;指向。

基本思想是引用的静态类型或指向对象的指针告诉编译器虚函数表的一部分是什么样的。当需要进行虚拟调度时,它只跟随此指针并决定调用哪个函数。假设你有一个基类B和派生类D1D2,如下所示:

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

D1D2的虚拟功能表将分别包含指向D1::f()D2::f()的合适指针。当编译器通过指针或对Bf()的引用看到调用时,它需要在运行时决定调用哪个函数:

void g(B* base) {
    base->f();
}

要解析调用,它会查看虚函数指针指向的位置并调用相应插槽中的函数(或多或少;虚函数表的内容往往是thunks,也可以进行任何必要的指针调整)

答案 2 :(得分:1)

“虚拟”表示“在运行时确定”。 “静态”表示“在翻译时确定”。

为了在运行时做出决策,您必须有一个参数(例如vptr),其值可以在运行时动态设置。也就是说,对于给定的基础对象引用x,我们需要一些包含动态信息的值“x.vptr”(即有关x是基础子对象的最派生类的信息)。

答案 3 :(得分:0)

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,但这是出乎意料的!