2

(C ++、MinGW 4.4.0、Windows OS)

ラベル<1>と<2>を除いて、コードでコメントされているのは私の推測です。私がどこか間違っていると思われる場合に備えて、私を訂正してください。

class A {
public:
   virtual void disp(); //not necessary to define as placeholder in vtable entry will be
                        //overwritten when derived class's vtable entry is prepared after
                        //invoking Base ctor (unless we do new A instead of new B in main() below)
};

class B :public A {
public:
   B() : x(100) {}
   void disp() {std::printf("%d",x);}
   int x;
};

int main() {
   A* aptr=new B;             //memory model and vtable of B (say vtbl_B) is assigned to aptr
   aptr->disp();              //<1> no error
   std::printf("%d",aptr->x); //<2> error -> A knows nothing about x
}

<2>はエラーであり、明らかです。なぜ<1>はエラーではないのですか?この呼び出しで起こっていると思うのはaptr->disp(); --> (*aptr->*(vtbl_B + offset to disp))(aptr) aptr、パラメーターがメンバー関数への暗黙のthisポインターであるということです。内部disp()にあるstd::printf("%d",x); --> std::printf("%d",aptr->x); SAME AS std::printf("%d",this->x);ので、なぜ<1>はエラーを出さないのに<2>はエラーを出さないのですか?

(vtablesは実装固有のものであることは知っていますが、それでも質問する価値があると思います)

4

4 に答える 4

3

ルールは次のとおりです。

C++ では、動的ディスパッチは、メンバー変数ではなく、メンバー関数関数に対してのみ機能します。

メンバー変数の場合、コンパイラはその特定のクラスまたはその基本クラスでシンボル名を検索するだけです。

ケース 1 では、呼び出される適切なメソッドは、を取得し、適切なメソッドのアドレスを取得してから、適切なメンバー関数を呼び出すことによって決定されます。したがって、静的バインディングの場合、 動的ディスパッチは本質的に通常の代わりになります。vpt
fetch-fetch-callcall

ケース 2 の場合: コンパイラxは明らかにのスコープ内でのみ検索しthisますが、それを見つけることができず、エラーを報告します。

于 2012-04-24T06:47:55.570 に答える
3

thisaptr内部と同じではありませんB::disp。の他のメソッドと同様に、B::disp実装はthisasを取ります。ポインターを介して仮想メソッドを呼び出すと、最初に変換されます (値が変更される可能性があるため、呼び出し中に必ずしも等しいとは限りません)。B*BA*B*aptr

つまり、実際に起こることは次のようなものです

typedef void (A::*disp_fn_t)();
disp_fn_t methodPtr = aptr->vtable[index_of_disp]; // methodPtr == &B::disp

B* b = static_cast<B*>(aptr);
(b->*methodPtr)(); // same as b->disp()

より複雑な例については、この投稿http://blogs.msdn.com/b/oldnewthing/archive/2004/02/06/68695.aspxを確認してください。ここで、同じ を呼び出す可能性のある複数のAベースがある場合、MSVC は、それぞれが異なるオフセットでポインタをB::dispシフトすることで、異なるエントリ ポイントを生成します。A*もちろん、これは実装固有です。他のコンパイラは、たとえばオフセットを vtable のどこかに格納することを選択する場合があります。

于 2012-04-24T07:02:29.543 に答える
1

あなたは混乱しています。私には、あなたはより動的な言語から来たように思えます。

C++ では、コンパイルとランタイムは明確に分離されています。プログラムは、最初にコンパイルしてから実行する必要があります (これらの手順のいずれかが失敗する可能性があります)。


だから、逆に:

<2>コンパイルは静的情報に関するものであるため、コンパイルに失敗します。aptrは 型A*であるため、 のすべてのメソッドと属性はA、このポインターを介してアクセスできます。disp()noを宣言したのでx、呼び出しはdisp()コンパイルされますが、 no はありませんx

したがって、<2>の失敗はセマンティクスに関するものであり、それらは C++ 標準で定義されています。


にたどり着くと、 inの宣言<1>あるので動作します。これにより、関数の存在が保証されます ( で定義していないため、実際にはここにあることに注意してください)。disp()AA

実行時に何が起こるかは、C++ 標準によって意味的に定義されていますが、標準では実装のガイダンスは提供されていません。ほとんどの (すべてではないにしても) C++ コンパイラは、クラスごとの仮想テーブル + インスタンス戦略ごとの仮想ポインターを使用します。この場合、説明は正しく見えます。

ただし、これは純粋なランタイム実装であり、実行されたという事実は、プログラムがコンパイルされたという事実にさかのぼって影響を与えません。

于 2012-04-24T07:38:33.483 に答える
0
virtual void disp(); //not necessary to define as placeholder in vtable entry will be
                     //overwritten when derived class's vtable entry is prepared after
                     //invoking Base ctor (unless we do new A instead of new B in main() below)

あなたのコメントは厳密には正しくありません。仮想関数は、純粋でない限り(逆は必ずしも成り立たない)、 odrで使用されます。つまり、仮想関数の定義を指定する必要があります。定義を提供したくない場合は、純粋仮想関数にする必要があります。

これらの変更のいずれかを行うと、派生クラスで基本クラス関数がオーバーライドされるため、派生クラスが機能aptr->disp();して呼び出されます。ベースへのポインタを介して呼び出すため、ベースクラス関数はまだ存在している必要があります。は基本クラスのメンバーではないため、有効な式ではありません。disp()disp()xaptr->x

于 2012-04-24T06:57:21.340 に答える