2016-03-18 21 views
2

これは以前の多くの質問と似ていますが、回答を見つけることができなかったことを尋ねます。 BASE2の派生の具体的複数の継承の場合にvptrを選択する

#include <iostream> 
using namespace std; 

class Base1 { 
    public: 
     int b1_data; 
     virtual void b1_fn() {cout << "I am b1\n";} 
}; 
class Base2 { 
    public: 
     int b2_data; 
     virtual void b2_fn() {cout << "I am b2\n";} 
}; 
class Derived : public Base1, public Base2 { 
    public: 
     int d_data; 
     void b1_fn() {cout << "I am b1 of d\n";} 
     void b2_fn() {cout << "I am b2 of d\n";} 
}; 

int main() { 
    Derived *d = new Derived(); 
    Base1 *b1 = d; 
    /*My observation mentioned below is implementation dependant, for learning, 
    I assume, there is vtable for each class containing virtual function and in 
    case of multiple inheritance, there are multiple vtables based on number of 
    base classes(hence that many vptr in derived object mem layout)*/ 

    b1->b1_fn(); // invokes b1_fn of Derived because d points to 
       // start of d's memory layout and hence finds vtpr to 
       // Derived vtable for Base1(this is understood) 
    Base2 *b2 = d; 
    b2->b2_fn(); // invokes b2_fn of Derived but how? I know that it "somehow" 
       // gets the offset added to d to point to corresponding Base2 
       // type layout(which has vptr pointing to Derived vtable for 
       // Base2) present in d's memory layout. 
    return 0; 
} 

、どのようB2点)(b2_fnしてもらうためにvtableのためvptrするのでしょうか?私はgccからmemlayoutダンプを見てみましたが、それほど分かりませんでした。

+2

あなたの質問は、「GCCは複数の継承を持つ仮想関数をどのように実装していますか」です。 –

+0

はい、種類は限られていますが、複数の基底クラスがあり、すべてが同じオフセットで派生オブジェクトレイアウトにマップされていない場合、バインディングをどのように実行するのかを知りたいと思います。基本的に、この "オフセット"のソースは私が探しているものです。 –

答えて

2

複数継承の場合のコンパイラは、すべてのサブオブジェクトが適切なvtableを持つようにvtableを構築します。もちろん、これは(vtableを自分自身のように)実装依存であるが、それはそのように編成されます:

  • Base1オブジェクトがBase1::b1_fn
  • にユニークなポインタを含むvtableのにBase2オブジェクトを指し示すvptrを持っている持っていますBase2::b2_fn
  • に固有のポインタを含むvtableのにDerivedオブジェクトを指すvptrはに対応するvtableのレイアウト始まるのvtableを指すvptrを有しますしかし、それはBase2のvtableの欠けている要素でそれを拡張します。 「レイアウト」では、b1_fn()のポインタは同じオフセットになりますが、オーバーライドする関数を指している可能性があります。したがって、ここではテーブルにはDerived::b1_fnとそれに続いてDerived::b2_fnが含まれます。この組み合わされたレイアウトは、のサブオブジェクトがDerivedにあり、その子とvtableを共有できることを保証します。
  • しかしDerivedオブジェクトが2つのサブオブジェクトで構成されていますので、Base1サブオブジェクトはBase2ために必要なレイアウトを使用して、独自のvtableを有するであろうBase2サブオブジェクト、続いて、やはり代わりに、元の一方のBase2::b2_fnとされるであろう。 Base2ポインタへDerivedポインタをキャストすると

、コンパイラは、それが一定のコンパイル時に決定されたオフセットを適用することで、そのvtableのでBase2サブオブジェクトを指すようになります。

ところで、ダウンキャスティングを行う場合、コンパイラは同様にDerivedの開始点を見つけるために、他の方向の固定オフセットを使用します。固定オフセットの手法がもはや機能しなくなる仮想ベースを使用するまでは、これはすべて簡単です。 other SO answer

このDr.Dobb's articleは、いくつかの良い画像でこれらのレイアウトの最良の説明です。

+0

"_仮想ベースポインタを使用する必要があります_"これは単なる実装の選択であり、必要ではありません – curiousguy

+0

@curiousguyもちろんです!仮想ベースポインタとvptrはどちらもC++標準を実装するための実装メカニズムであり、標準には属していません。したがって、同じ効果を達成する任意の代替方法を使用することができる。好奇心のために、具体的なことを念頭においてください。 – Christophe

+0

vptrに実行可能な代替手段はありませんが、仮想ベースポインタには明白な代替手段があります。仮想ベースポインタがありません。仮想ベースのオフセットはvtableにあります。 GCCのルール – curiousguy