vtableを用いたOOP言語の実装
これの補足。
上記記事では実行時にクラスを判定することで多態を実現していたけど、別の方法としてvtableという仕組みがあるようだ。
vtableとは
C++やSwiftで使われている方式。
LLVMでの実装は以下が参考になる。
- クラスIDの代わりにオブジェクトにvtableへの参照を入れておく
- メソッドを呼びだす際は、vtableのn番目の関数ポインタを取得して実行する
- nはメソッドごとに一意
例
例として、「図形」を表すShapeクラスと、そのサブクラスであるCircleクラスおよびSquareクラスがあるとしよう。各クラスは面積を計算して返すareaというメソッドをもつとする。
この例でいえば、Circleのインスタンスはvtableの0番目にCircle#areaの関数ポインタをもち、Squareのインスタンスはvtableの0番目にSquare#areaの関数ポインタをもつようにする。
Shapeの配列から要素を一つずつ取り出してareaを計算するとき、取り出したものがCircleかSquareか確認する必要はなく、vtableの0番目の関数を実行すればよい。
ちなみに
実際の処理系ではareaはたぶん0番目ではなくて、Objectクラスのメソッドが0番目に来ると思われる。vtableには継承階層の上の方から順に関数を詰めていくからだ。
またこのことから、RubyでいうModule・Javaでいうinterface・SwiftでいうProtocolの実装はこの方法ではうまく行かないことがわかる。これらは継承階層とは関係なくクラスに直接メソッドを生やせるので、vtable内での位置を同じにできないのだ。
Javaの場合
invokeinterfaceという専用のVM命令があるようだ。
invokeinterfaceが使われる場合は、もう少し複雑になります。たとえば、すべてのFurryの実装でgroom()メ ソッドが必ずしもvtableの同じ場所にあるとは限りません。Cat::groomとBear::groomでオフセットが異なるの は、それぞれのクラスの継承階層が異なるためです。そのため、コンパイル時にインタフェース型しかわからないオブジェクトのメソッドを呼び出す場合、追加でルックアップを行う必要があります。 インタフェースの呼出しの際に、追加のルックアップ作業が若干必要にはなりますが、インタフェースの使用を 避けて微少な最適化を試みるべきではないという点に注意してください。JVMにはJITコンパイラが搭載されているため、この2つのケースに実質的なパフォーマンスの差はないことを覚えておいてください。
Swiftの場合
Witnessテーブルというものを使うようだ。
Witnessテーブルは、プロトコルに準拠している場合に、どの型がどのプロトコルに準拠しているのか、という情報が記録されています。 メソッド呼び出し時には、Witnessテーブルを使用して、呼び出すメソッドを決定します。
またSwiftにはextensionという、既存のクラスを拡張する仕組みがある。同記事によるとこの場合はstatic dispatchになるようだ(extensionメソッドはオーバーライドされることがないので、呼び出すべきメソッドが静的に解決できる)。