うまくいけば、これはStackOverflowの質問にあまり特化されていません。それが他の場所に移行できる場合は、私に知らせてください...
何ヶ月も前に、私はC ++および関連言語のさまざまな仮想化解除手法を提案する学部の論文を書きました。これは、一般にコードパスのコンパイル済みの特殊化(テンプレートのようなもの)のアイデアに基づいていますが、実行時に正しい特殊化を選択するためのチェックがあります。コンパイル時に選択することはできません(テンプレートが選択されている必要があるため)。
(非常に)基本的な考え方は次のようなものです...次のC
ようなクラスがあるとします。
class C : public SomeInterface
{
public:
C(Foo * f) : _f(f) { }
virtual void quack()
{
_f->bark();
}
virtual void moo()
{
quack(); // a virtual call on this because quack() might be overloaded
}
// lots more virtual functions that call virtual functions on *_f or this
private:
Foo * const _f; // technically doesn't have to be const explicitly
// as long as it can be proven not be modified
};
そして、既知の完全な型を持つ、Foo
などの具体的なサブクラスが存在することを知っていた場合(必ずしも完全なリストを持っている必要はありません)、たとえば、のいくつかの選択されたサブクラスの特殊なバージョンをプリコンパイルできます(コンストラクターは呼び出されないため、意図的にここに含まれています):FooA
FooB
C
Foo
class C_FooA final : public SomeInterface
{
public:
virtual void quack() final
{
_f->FooA::bark(); // non-polymorphic, statically bound
}
virtual void moo() final
{
C_FooA::quack(); // also static, because C_FooA is final
// _f->FooA::bark(); // or you could even do this instead
}
// more virtual functions all specialized for FooA (*_f) and C_FooA (this)
private:
FooA * const _f;
};
そして、のコンストラクターをC
次のようなものに置き換えます。
C::C(Foo * f) : _f(f)
{
if(f->vptr == vtable_of_FooA) // obviously not Standard C++
this->vptr = vtable_of_C_FooA;
else if(f->vptr == vtable_of_FooB)
this->vptr = vtable_of_C_FooB;
// otherwise leave vptr unchanged for all other values of f->vptr
}
したがって、基本的に、構築されるオブジェクトの動的タイプは、コンストラクターへの引数の動的タイプに基づいて変更されます。(コンパイル時C<Foo>
にタイプがわかっている場合にのみ作成できるため、テンプレートを使用してこれを行うことはできません)。f
今後、FooA::bark()
スルーへの呼び出しにC::quack()
は1つの仮想呼び出しのみが含まれます。への呼び出しは動的に呼び出す非特殊バージョンに静的にバインドされるか、への呼び出しはC::quack()
静的に呼び出すに動的に転送されます。さらに、フローアナライザが静的呼び出しを行うのに十分な情報を持っている場合、動的ディスパッチが完全に排除される場合があります。FooA::bark()
C::quack()
C_FooA::quack()
FooA::bark()
C_FooA::quack()
、インライン化が可能であれば、タイトなループで非常に役立つ可能性があります。(技術的には、その時点では、この最適化がなくてもおそらく大丈夫でしょう...)
(この変換は安全ですが、あまり有用ではありませんが_f
、プライベートではなく非定数で保護されておりC
、別の変換ユニットから継承されていることに注意してください...継承されたクラスのvtableを作成する変換ユニットはで何も知りません特殊化に関するすべてと、継承されたクラスのコンストラクターは、this->vptr
を独自のvtableに設定するだけです。これは、特殊化された関数について何も知らないため、それらを参照しません。)
これは、1レベルの間接参照を排除するための多大な労力のように思われるかもしれませんが、ポイントは、内部のローカル情報のみに基づいて、任意のネストレベル(このパターンに従う仮想呼び出しの深さを1に減らすことができます)にそれを実行できるということです。翻訳ユニット、そしてあなたが知らない他の翻訳ユニットで新しいタイプが定義されている場合でも回復力のある方法でそれを行います...あなたはあなたがそうでなければ持っていないであろう多くのコード膨張を追加するかもしれません素朴にやった。
とにかく、この種の最適化が実際に実装の努力に見合うだけの価値があり、結果として得られる実行可能ファイルのスペースオーバーヘッドに見合うだけの価値があるかどうかに関係なく、私の質問は、標準C++に防止するものはありますか?コンパイラはそのような変換を実行しませんか?
標準では、仮想ディスパッチがどのように行われるか、またはメンバー関数へのポインターがどのように表されるかがまったく指定されていないため、私の気持ちは違います。仮想テーブルが異なっていても、RTTIメカニズムがすべての目的で同じタイプになりすますことを妨げC
たり妨げたりすることはないと確信しています。C_FooA
私が考えることができる他の唯一のことは、おそらく問題になる可能性がありますが、ODRをよく読むことですが、おそらくそうではありません。
私は何かを見落としていますか?ABI /リンクの問題を除けば、このような変換は、準拠しているC ++プログラムを壊すことなく可能でしょうか?(さらに、「はい」の場合、これは現在Itaniumおよび/またはMSVC ABIで実行できますか?答えも「はい」であると確信していますが、誰かが確認できることを願っています。)
編集:このようなものがC ++、Java、またはC#用の主流のコンパイラ/ JITに実装されているかどうか誰かが知っていますか?(以下のコメントのディスカッションとリンクされたチャットを参照してください...)JITが呼び出しサイトで仮想の投機的な静的バインディング/インライン化を直接行うことは知っていますが、(まったく新しいvtablesを使用して)このようなことを行うかどうかはわかりません各呼び出しサイトではなく、コンストラクターで実行される単一の型チェックに基づいて生成および選択されます)。