8

この質問は私の頭の周りに浮かぶ大きな疑問の1つであり、言葉で説明するのも難しいです。明白な場合もあれば、クラックするのが難しい場合もあるので、質問は次のようになります::

クラスベース{
公衆:
     int a_number;

     ベース(){}
     virtual void function1(){}
     virtual void function2(){}
     void function3(){}
};

class Derived:public Base {
公衆:
     Derived():Base(){}
     void function1(){cout<<"ベースから派生"<<endl;
     virtual void function4(){cout<<"派生のみ"<<endl;}
};

int main(){

      派生*der_ptr= new Derived();
      ベース*b_ptr= der_ptr; //アドレスだけが渡されているため、b_ptrは派生オブジェクトを指します

      b_ptr-> function4(); //コンパイルエラーが発生します!!

      b_ptr-> function1(); //Derivedクラスのオーバーライドされたメソッドを呼び出します

      0を返します。

}

Q1。b_ptrは派生オブジェクトを指していますが、どのVTABLEにアクセスし、どのようにアクセスしますか?b_ptr-> function4()はコンパイルエラーを出します。または、b_ptrが派生VTABLEの基本クラスVTABLEのサイズまでしかアクセスできないということですか?

Q2。Derivedのメモリレイアウトは(Base、Derived)でなければならないので、BaseクラスのVTABLEもDerivedクラスのメモリレイアウトに含まれていますか?

Q3。基本クラスVtableのfunction1とfunction2は基本クラスの実装を指し、派生クラスのfunction2は基本クラスのfunction2を指しているので、基本クラスにVTABLEが本当に必要ですか?(これは私が今までに尋ねることができる最もばかげた質問かもしれませんが、それでも私は現在の状態でこれについて疑問があり、答えはQ1の答えに関連している必要があります:))

コメントしてください。

しばらくお待ちいただきますようお願いいたします。

4

6 に答える 6

8

さらなる例として、これはC ++プログラムのCバージョンであり、vtablesとすべてを示しています。

#include <stdlib.h>
#include <stdio.h>

typedef struct Base Base;
struct Base_vtable_layout{
    void (*function1)(Base*);
    void (*function2)(Base*);
};

struct Base{
    struct Base_vtable_layout* vtable_ptr;
    int a_number;
};

void Base_function1(Base* this){}

void Base_function2(Base* this){}

void Base_function3(Base* this){}

struct Base_vtable_layout Base_vtable = {
    &Base_function1,
    &Base_function2
};

void Base_Base(Base* this){
    this->vtable_ptr = &Base_vtable;
};

Base* new_Base(){
    Base *res = (Base*)malloc(sizeof(Base));
    Base_Base(res);
    return res;
}

typedef struct Derived Derived;
struct Derived_vtable_layout{
    struct Base_vtable_layout base;
    void (*function4)(Derived*);
};

struct Derived{
    struct Base base;
};

void Derived_function1(Base* _this){
    Derived *this = (Derived*)_this;
    printf("Derived from Base\n");
}

void Derived_function4(Derived* this){
    printf("Only in derived\n");
}

struct Derived_vtable_layout Derived_vtable = 
{
    { &Derived_function1,
      &Base_function2},
    &Derived_function4
};

void Derived_Derived(Derived* this)
{
    Base_Base((Base*)this);
    this->base.vtable_ptr = (struct Base_vtable_layout*)&Derived_vtable;
}      

Derived* new_Derived(){
    Derived *res = (Derived*)malloc(sizeof(Derived));
    Derived_Derived(res);
    return res;
}



int main(){
      Derived *der_ptr = new_Derived();
      Base *b_ptr = &der_ptr->base;
      /* b_ptr->vtable_ptr->function4(b_ptr); Will Give Compilation ERROR!! */
      b_ptr->vtable_ptr->function1(b_ptr);
      return 0;
}
于 2010-12-28T18:33:22.963 に答える
3

Q1-名前解決は静的です。b_ptrのタイプはBase*であるため、コンパイラーは、v_table内のエントリーにアクセスするために、Derivedに固有の名前を確認できません。

Q2-たぶん、そうではないかもしれません。vtable自体は、ランタイムポリモーフィズムを実装するための非常に一般的な方法であり、実際にはどこにも標準の一部ではないことを覚えておく必要があります。それがどこにあるかについて明確な声明を出すことはできません。vtableは、実際には、インスタンスのオブジェクト記述内からポイントされるプログラムのどこかにある静的テーブルである可能性があります。

Q3-仮想エントリが1つの場所にある場合は、すべての場所にある必要があります。そうしないと、オーバーライド機能を提供するために、困難/不可能なチェックが多数必要になります。コンパイラがBaseを持っていて、オーバーライドされた関数を呼び出していることを知っている場合は、vtableにアクセスする必要はありませんが、関数を直接使用できます。必要に応じてインライン化することもできます。

于 2010-12-28T18:03:29.407 に答える
3

A1。vtableポインターは派生vtableを指していますが、コンパイラーはそれを認識していません。Baseポインターとして扱うように指示したので、ポインターが何を指しているかに関係なく、Baseクラスに有効なメソッドのみを呼び出すことができます。

A2。vtableレイアウトは標準で指定されておらず、正式にはクラスの一部でもありません。これは、99.99%の最も一般的な実装方法です。vtableはオブジェクトレイアウトの一部ではありませんが、オブジェクトの非表示メンバーであるvtableへのポインターがあります。これは常にオブジェクト内の同じ相対位置にあるため、コンパイラは、クラスポインタに関係なく、常にオブジェクトにアクセスするためのコードを生成できます。多重継承で物事はより複雑になりますが、まだそこに行くことはできません。

A3。Vtableは、オブジェクトごとに1回ではなく、クラスごとに1回存在します。コンパイラは、使用されない場合でも、事前にそれを認識していないため、生成する必要があります。

于 2010-12-28T18:11:12.620 に答える
1

b_ptrは派生vtableを指します-しかし、コンパイラは派生クラスにfunction_4が含まれていることを保証できません。これは、ベースvtableに含まれていないため、コンパイラは呼び出しを行う方法を知らず、エラーをスローします。

いいえ、vtableはプログラム内のどこかにある静的定数です。基本クラスは、それへのポインタを保持するだけです。Derivedクラスは2つのvtableポインターを保持できますが、そうではない場合があります。

これらの2つのクラスのコンテキストでは、BaseはDerivedのfunction1を見つけるためにvtableを必要とします。これは、基本クラスの仮想関数を上書きするため、マークを付けなくても実際には仮想です。ただし、そうでない場合でも、コンパイラがvtableを生成する必要があることは間違いありません。これらのクラスから継承する場合としない場合がある、他の変換ユニットにある他のクラスがわからないためです。未知の方法で仮想関数をオーバーライドします。

于 2010-12-28T18:06:17.033 に答える
1

まず、そして最も重要なこととして、C++はいかなる種類のランタイムイントロスペクションもあまり行わないことを忘れないでください。基本的に、コンパイル時にオブジェクトに関するすべてを知る必要があります。

Q1-b_ptrはベースへのポインタです。したがって、Baseオブジェクトに存在するものにのみアクセスできます。例外なし。現在、実際の実装はオブジェクトの実際のタイプに応じて変わる可能性がありますが、Baseポインターを介してメソッドを呼び出す場合は、Baseでメソッドを定義する必要はありません。

Q2-簡単な答えは「はい、ベースのvtableは派生物に存在する必要があります」ですが、vtableをレイアウトする方法については多くの可能な戦略があるので、その正確な構造にとらわれないでください。

Q3-はい、Baseクラスにvtableが必要です。クラス内の仮想関数を呼び出すものはすべてvtableを通過するため、基になるオブジェクトが実際にDerivedである場合は、すべてが機能します。

これは絶対的なものではありません。コンパイラが取得したものを完全に認識できる場合(ローカルスタックで宣言されたBaseオブジェクトの場合のように)、コンパイラはvtableルックアップを最適化することができます。また、関数をインライン化することも許可される場合があります。

于 2010-12-28T18:13:29.957 に答える
1

これはすべて、実装によって異なります。しかし、ここに「vtables」を使用する通常の最も簡単な方法の答えがあります。

クラスにはBasevtableポインターがあるため、基になる表現は次のような擬似コードです。

struct Base {
  void** __vtable;
  int a_number;
};
void* __Base_vtable[] = { &Base::function1, &Base::function2 };
void __Base_ctor( struct Base* this_ptr ) { this_ptr->__vtable = __Base_vtable; }

Derivedクラスには、Baseクラスサブオブジェクトが含まれます。これにはvtableの場所があるため、Derived別のvtableを追加する必要はありません。

struct Derived {
  struct Base __base;
};
void* __Derived_vtable[] =
  { &Derived::function1, &Base::function2, &Derived::function4 };
void __Derived_ctor( struct Derived* this_ptr ) {
  __Base_ctor( &this_ptr->__base );
  this_ptr->__base.__vtable = __Derived_vtable;
}

私の擬似コードでは、誰かがまたは__Base_vtableを試行した場合に備えて、「基本クラスのvtable」が必要です。new Base();Base obj;

多重継承または仮想継承が含まれる場合、上記のすべてがより複雑になります。

この行のb_ptr -> function4();場合、これはコンパイル時のエラーであり、vtablesとはあまり関係がありません。ポインターにキャストするときはBase*、によって定義された方法でのみそのポインターを使用できます(コンパイラーは、それが本当に、、、または他のクラスであるclass Baseかどうかを「認識」しないため)。独自のデータメンバーがある場合、そのポインタを介してアクセスすることはできません。仮想かどうかに関係なく、独自のメンバー関数がある場合、そのポインターを介してアクセスすることはできません。DerivedBaseDerivedDerived

于 2010-12-28T18:20:44.653 に答える