3

これは以前の多くの質問に似ていますが、私が答えを見つけることができなかった何かを尋ねます.

#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_fn() に到達するために、Base2 の Derived の vtable の vptr を b2 がどのように指すのでしょうか? gcc から memlayout ダンプを見てみましたが、よくわかりませんでした。

4

1 に答える 1

3

多重継承の場合、コンパイラは、すべてのサブオブジェクトが適切な vtable を持つように vtables を構築します。もちろん、これは (vtables 自体のように) 実装に依存しますが、次のように編成されます。

  • Base1オブジェクトには、一意のポインターを含む vtable を指す vptr があります。Base1::b1_fn
  • Base2オブジェクトには、一意のポインターを含む vtable を指す vptr があります。Base2::b2_fn
  • オブジェクトには、に対応するvtableレイアウトDerivedで始まる vtable を指す vptr がありますが、 の vtableの欠落要素で拡張されています。「レイアウト」とは、ポインターが同じオフセットにあることを意味しますが、オーバーライド関数を指している可能性があります。したがって、ここでは、表に が続きます。この結合されたレイアウトにより、 のサブオブジェクトがvtable をその子と共有できるようになります。 Base1Base2b1_fn()Derived::b1_fnDerived::b2_fnBase1Derived
  • ただし、Derivedオブジェクトは 2 つのサブオブジェクトで構成されます。したがって、Base1サブオブジェクトの後にサブオブジェクトが続きます。このBase2サブオブジェクトは、独自の vtable を持ち、 に必要なレイアウトを使用しますBase2Base2::b2_fn、元のレイアウトの代わりに を使用します。

Derivedポインターをポインターにキャストする場合Base2、コンパイラーは、Base2コンパイル時に決定された固定オフセットを適用することにより、vtable を使用してサブオブジェクトを指すようにします。

ちなみに、ダウンキャストを行う場合、コンパイラは同様に固定オフセットを反対方向に使用して、Derived. これは、固定オフセットの手法が機能しなくなった仮想ベースを使用するまでは、すべて非常に単純です。この他のSO回答で説明されているように、仮想ベースポインターを使用する必要があります

このDr.Dobb の記事は、これらすべてのレイアウトについて、いくつかの優れた写真を使用して説明するのに最適です。

于 2016-03-18T18:22:57.983 に答える