18

うまくいけば、これは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などの具体的なサブクラスが存在することを知っていた場合(必ずしも完全なリストを持っている必要はありません)、たとえば、のいくつかの選択されたサブクラスの特殊なバージョンをプリコンパイルできます(コンストラクターは呼び出されないため、意図的にここに含まれています):FooAFooBCFoo

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を使用して)このようなことを行うかどうかはわかりません各呼び出しサイトではなく、コンストラクターで実行される単一の型チェックに基づいて生成および選択されます)。

4

2 に答える 2

1

標準C++には、コンパイラがそのような変換を実行するのを妨げるものはありますか?

観察可能な振る舞いが変更されていないことが確実な場合はそうではありません。これは、標準セクション1.9である「as-ifルール」です。

しかし、これはあなたの変換が正しいことを証明することをかなり難しくするかもしれません:12.7 / 4:

仮想関数がコンストラクター(非静的データメンバーの場合はmem-initializerまたはbrace-or-equal-initializerを含む)またはデストラクタから直接または間接的に呼び出され、呼び出しが適用されるオブジェクトがオブジェクトである場合構築中または破棄中、呼び出される関数は、コンストラクタまたはデストラクタ自体のクラスまたはそのベースの1つで定義されたものですが、コンストラクタまたはデストラクタ自体のクラスから派生したクラスでオーバーライドしたり、いずれかでオーバーライドしたりする関数ではありません。最も派生したオブジェクトの他の基本クラス。

したがって、デストラクタがオブジェクトFoo::~Foo()を直接または間接的に呼び出すC::quack()場合、オブジェクトcc._fポイントする場合は、オブジェクトを作成したときFoo::bark()であっても、を呼び出す必要があります。_fFooAc

于 2013-03-01T17:32:53.783 に答える
0

最初に読んだとき、これは多形インラインキャッシングのC++に焦点を当てたバリエーションのように聞こえます。V8とOracleのJVMの両方がそれを使用していると思いますが、 .NETが使用していることは知っています。

あなたの最初の質問に答えるために:私は、これらの種類の実装を禁止する規格には何もないと思います。C ++は、「現状のまま」のルールを非常に真剣に受け止めています。適切なセマンティクスを忠実に実装する限り、好きな方法で実装を行うことができます。C ++の仮想呼び出しはそれほど複雑ではないので、そこでもエッジケースにつまずくとは思えません(たとえば、静的バインディングで何か賢いことをしようとしていた場合とは異なります)。

于 2013-03-01T15:45:27.557 に答える