4

一部のコンパイラでは、クラスに仮想関数がある場合、そのオブジェクトの最初のバイトのアドレスを使用してその vptr にアクセスできます。例えば、

class Base{
public:
    virtual void f(){cout<<"f()"<<endl;};
    virtual void g(){cout<<"g()"<<endl;};
    virtual void h(){cout<<"h()"<<endl;};
};

int main()
{   
   Base b;

   cout<<"Address of vtbl:"<<(int *)(&b)<<endl;

   return 0;
}

さまざまなコンパイラの動作に依存していることはわかっています。一番最初のエントリとして vptr が格納される場合があるので、これを行う利点は何ですか? それはパフォーマンスの向上に役立ちますか、それとも単に &b を使用して vbtl にアクセスする方が簡単だからですか?

4

2 に答える 2

4

これは実装の詳細ですが、実際には多くの実装がこれを行っています。

かなり効率的で便利です。特定のオブジェクトに対して仮想関数を呼び出す必要があるとします。そのオブジェクトへのポインタと仮想関数インデックスがあります。そのインデックスとこのオブジェクトに対してどの関数を呼び出す必要があるかをどうにかして見つける必要があります。さて、sizeof(void*)ポインタの後ろの最初のバイトにアクセスし、vtable が存在する場所を見つけてから、vtable の必要な要素にアクセスして関数アドレスをフェッチするだけです。

「各オブジェクトのvtable」などの個別のマップを保存できますが、vptrをオブジェクト内に保存することにした場合は、最後のバイトやその他の場所ではなく、最初のバイトを使用するのが論理的です。オブジェクトへのポインターを取得したら、vptr を見つける場所がわかります。追加のデータは必要ありません。

于 2015-11-06T07:33:02.533 に答える
3

これは実装定義ですが、実際の選択肢はあまりないようです。

まず第一に、あなたが持っている必要があることを見ることができますvptrまたは 埋め込まれvtableた . 後者は、on コンストラクションをコピーする必要がありvtable、より多くのメモリを消費することを意味しますが、各メソッド呼び出しで 1 つのポインター逆参照を回避できるという利点があります。状況に応じて、おそらくどちらにも適切な議論があります。ほとんどの実装では、ディスパッチ時間を節約する代わりに、構築時間と全体的なメモリ消費を削減することを選択しています。

アプローチを選択vptrすると、基本クラスと派生クラスのレイアウトのバイナリ互換性を維持する必要があることがわかります。まず第一に、(多くの場合) one を使用してこれを実現できます。互換性の理由からvptr、これは最も基本的なクラスに存在する必要があります。vptr

単純な継承を処理する場合、派生クラスから基本クラスへの変換の最も簡単な方法は、ポインター値を保持することです。これは、レイアウトが最初に基本クラスのフィールドでなければならず、その後に追加の派生クラスがそれに寄与する必要があることを意味します。

これで、最初にする理由にかなり近づきましたvptr。オブジェクトの最も基本的な部分内に存在する必要があるため、オブジェクトの先頭近くにある必要があります。

それをオフセット 0 に置く理由は、それがすべてのクラスで利用できる一貫したオフセットだからかもしれません。の前に配置できるデータがあるという保証はありませんvptr

をオフセットvptr0 に配置すると、いくつかの利点もあります。オブジェクトに があるvptrことがわかっている場合は、オブジェクトのタイプを知る必要なくオフセット 0 を見なければならないことがわかります (オブジェクトが を持っている以上vptr)。これは、デバッグ目的で便利です (vtable多くの場合、実際の型を推測するのに十分な情報が含まれています)。特に、定義済みのオフセットを介してノードtypeidを取得するために同じオフセットを確認するだけでよいため、 および 同様の実装が簡単になります。type_infoつまり、 の実際のコードを共有できるということですtypeid

于 2015-11-06T07:33:36.670 に答える