


仮想関数テーブル(かそうかんすうテーブル、)あるいはvtableは、プログラミング言語の実装において動的なポリモーフィズム、すなわち実行時のメソッドの束縛を実現するために用いられる機構である。あるプログラムが継承関係にある複数のクラスを持っているとする。たとえばスーパークラス codice_1 と二つのサブクラス codice_2 と codice_3 において、クラス codice_1 が "speak" という仮想関数を定義しており、サブクラスは適切な実装(鳴く、吠えるといった)を行うものとする。プログラムが "speak" メソッドを Cat ポインタ(Cat クラスと Cat の任意のサブクラスを示すことができる) に対して呼び出すと、実行環境は、Cat が示す実際のオブジェクトの種類に応じてどの実装を呼び出すかを決定しなければならない。このような動的な割り当てを実現するには様々な方法があるが、vtable による方法が C++ や関連するプログラミング言語(D言語 や C# など)では一般的である。実用的なオブジェクトのインターフェイスを実装と分離する言語(Visual Basic や Delphi など)でも、オブジェクトが異なる関数ポインタのセットを用いるだけで異なる実装を用いることができるため、vtable による方法を用いる傾向にある。C++ の標準では、動的なディスパッチがどのように実装されるべきかについて規定していないが、一般的にコンパイラは若干の変更を加えて共通の基本的なモデルを用いる。典型的には、コンパイラは各クラスごとに別の vtable を作成する。オブジェクトが生成される際、vtable に対するポインタ、仮想テーブルポインタ, vpointer, vptr がオブジェクトの不可視のメンバーとして追加される(通常は最初のメンバーとなる)。コンパイラはコンストラクタ内に"隠れた"コードを生成し、クラスのオブジェクトの vpointer が、対応する vtable のアドレスで初期化されるようにする。下記のクラスの宣言は C++ の文法に従うものとする:これらは下記のクラスを派生させる。GNUコンパイラコレクション の g++ 3.4.6 は、オブジェクト codice_5 に対して下記の 32bit メモリレイアウトを生成する。オブジェクト codice_6 のメモリレイアウト:仮想でない関数(codice_7) は通常 vtable に現れないが、いくつかの例外もある(デフォルトコンストラクタなど)。クラス codice_8 でメソッド codice_9 をオーバーライドしているが、これは codice_10の仮想関数テーブルを複製し、codice_11 へのポインタを codice_12 へのポインタで置き換えることで実現されている。g++ コンパイラはクラス codice_8 の codice_14 と codice_10 からの多重継承を、基底クラスごとに一つずつの、二つの仮想関数テーブルを用いて実現する(多重継承を実現するには他にも方法があるがこれが最も一般的である)。これにより、キャストの際 " () が必要になる。下記のような C++ コードを考える:codice_6 と codice_17 が実行時に同じメモリ位置を参照するが、codice_5 は codice_19 (codice_6 のメモリ配置の 8バイト後方)を示す。そのため、codice_5 は codice_6 内の codice_10 らしく見える領域、すなわち codice_10 のインスタンスと同じメモリレイアウトを持つ部分を示す。呼び出しの際には、codice_25 の呼び出しの際は、codice_6 の codice_27 vpointer をたどり、vtable から codice_28 のエントリーを調べ、ポインタを取り出してコードを呼び出す。単一継承(あるいは、単一継承のみ可能な言語)の場合、vpointer が常に codice_6 の最初の要素にあれば(多数のコンパイラでそうなっている)、下記のような擬似 C++ のコードに簡略化できる。より一般的なケースでは、上記のようなcodice_6の codice_31、codice_12、codice_11 呼び出しはより複雑なものとなる。これに対して、codice_34 の呼び出しはもっと単純である:単なるコンパイルされたポインタへのジャンプである非仮想関数の呼び出しに対して、仮想関数の呼び出しは最低一度以上、余分にポインタをたどる操作や"fixup" が必要である。そのため、仮想関数の呼び出しは原理的に非仮想の関数呼び出しに対して低速である。実験によれば 6-13% の実行時間が単なる関数のディスパッチに用いられ、オーバーヘッドは場合によって 50% に達する。さらに、 JIT コンパイルが使用できない環境では、仮想関数は通常インライン展開できない。テーブルの参照を行う部分を、たとえばインライン化された本体部分を条件文で実行させることも可能ではあるが、そうした最適化は一般的ではない。オーバーヘッドを避けるため、コンパイラはコンパイル時に呼び出しが解決できる場合には vtable の生成を行わない。従って、上記の codice_28 の呼び出しは、 codice_6 が現時点で codice_8 のみ保持しており、codice_8 が codice_28 をオーバーライドしないことをコンパイラが判断できるため、vtable の検索は必要なくなる可能性がある。コンパイラ(あるいは最適化プログラム)はプログラム内に codice_28 をオーバーライドする codice_14 のサブクラスがないことを検出することができるかもしれない。実装が明示的に指定されているため(this ポインタの fixup が必要ではあるが)codice_42 ないし codice_43 はおそらく vtable の検索を必要とすることはない。vtable は一般的に動的なディスパッチを実現するための、性能上のよいトレードオフであるが、たとえば二分木ディスパッチといった代替の方法も存在する。しかし、vtable は特殊な "this" パラメータでは のみ考慮しており、ディスパッチの際全てのパラメータの型が考慮される多重ディスパッチ(CLOS や Dylan )とは異なる。vtables はまた、コンパイル時に単一の配列にメソッドを配置するため、ディスパッチが既知のメソッドのセットに限定されている場合のみうまく動作する。これはダック・タイピング言語(Smalltalk、Python、JavaScript、あるいは C++ のコンパイル時のテンプレート機構)とは対照的である。上記の一つ、ないし両方をサポートする言語は、ディスパッチをハッシュテーブルの文字列検索や同等の手段で行うことが多い。ディスパッチを高速化する様々な方法があり(たとえば、メソッドの名前を intern 化やトークン化する、検索のキャッシュ、 JIT コンパイルなど)、ディスパッチの時間は全体的な処理時間にそれほどの影響を与えない。それでもなお、vtable の検索の方が明らかに高速である。また vtable は実装やデバッグが簡単で、文字列のハッシュテーブルよりも "C の精神"に近い。
出典:wikipedia
LINEスタンプ制作に興味がある場合は、
下記よりスタンプファクトリーのホームページをご覧ください。