仮想関数テーブルがクラスのすべてのオブジェクトで同じである場合、そのテーブルへのポインター (vfptr) を静的にしてすべてのオブジェクトで共有できないのはなぜですか?
4 に答える
vtable は本質的に静的です。ただし、仮想ディスパッチやその他の RTTI 操作を行うには、実際にはオブジェクト内に vptr メンバーが必要です。
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();
}
呼び出しを解決するために、仮想関数ポインターが指している場所を調べ、適切なスロットで関数を呼び出します (多かれ少なかれ、仮想関数テーブルの内容は、必要なポインター調整も行うサンクになる傾向があります)。
「仮想」とは「実行時に決定される」ことを意味します。「静的」とは、「変換時に決定される」ことを意味します。
実行時に決定を下すには、実行時に値を動的に設定できるパラメーター (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 はコンパイラによって認識され、使用されますが、これは予想外です!