コンパイラがクラス内の仮想関数に遭遇するたびに、コンパイラは Vtable を作成し、そこに仮想関数のアドレスを配置します。継承する他のクラスでも同様です。各 Vtable を指す各クラスに新しいポインタを作成しますか? そうでない場合、派生クラスの新しいインスタンスが作成されて Base PTR に割り当てられたときに、仮想関数にどのようにアクセスしますか?
3 に答える
仮想関数を含むクラスを作成するか、仮想関数を含むクラスから派生するたびに、コンパイラはそのクラスに固有の VTABLE を作成します。
基本クラスで virtual として宣言された関数をオーバーライドしない場合、コンパイラは派生クラスの基本クラス バージョンのアドレスを使用します。
次に、VPTR をクラスに配置します。単純継承を使用する場合、オブジェクトごとに VPTR は 1 つだけです。VPTR は、適切な VTABLE の開始アドレスを指すように初期化する必要があります。(これはコンストラクターで発生します。) VPTR が適切な VTABLE に初期化されると、オブジェクトは事実上、それがどのタイプであるかを「認識」します。しかし、この自己認識は、仮想関数が呼び出される時点で使用されない限り、価値がありません。基本クラス アドレスを介して仮想関数を呼び出すと (コンパイラが事前バインディングを実行するために必要なすべての情報を持っていない状況)、何か特別なことが起こります。特定のアドレスへのアセンブリ言語 CALL である典型的な関数呼び出しを実行する代わりに、コンパイラは関数呼び出しを実行する別のコードを生成します。
仮想関数を持つクラスごとに、vtable が作成されます。次に、実行可能なクラスのオブジェクトがコンストラクターを使用して作成されると、コンストラクターは適切な vtable をオブジェクトにコピーします。そのため、各オブジェクトにはその vtable へのポインターがあります (多重継承の場合は、必要に応じて各 vtable への Orr を持ちます)。コンパイラは、オブジェクト内の vtable の場所を認識しているため、仮想メソッドを呼び出す必要がある場合は、バイト コードを出力して vtable を抑止し、適切なメソッドを検索して、そのアドレスにジャンプします。
単一継承の単純なケースでは、子クラスは親クラスの vtable のコピーから始まり、親クラスのメソッドをオーバーライドする子クラスの仮想メソッドごとにオーバーライドされたエントリを取得します。(また、親クラス メソッドをオーバーライドしない子クラッド内のすべての仮想関数の新しいエントリも取得します)。
プログラムがコンパイルされるたびに、各クラスの仮想テーブルが作成されます。これにより、vtable がクラスごとに作成されるという事実が明確になります。実行時にオブジェクトが作成されると、コンパイラは vptr をオブジェクトに割り当てます。これは、特定のクラスのオブジェクトの仮想テーブルを指します。つまり、vptr はオブジェクトごとに作成されます。